Generatory tak leniwie oceniają return
lub yield
zachowują się inaczej podczas debugowania kodu lub zgłaszania wyjątku.
Z return
każdym wyjątkiem, który zdarzy się w twoim generator
przypadku, nie będziesz o niczym wiedział generate_all
, to dlatego, że kiedy generator
naprawdę zostanie wykonany, opuściłeś już tę generate_all
funkcję. Z yield
tam będzie miał generate_all
ślad.
def generator(some_list):
for i in some_list:
raise Exception('exception happened :-)')
yield i
def generate_all():
some_list = [1,2,3]
return generator(some_list)
for item in generate_all():
...
Exception Traceback (most recent call last)
<ipython-input-3-b19085eab3e1> in <module>
8 return generator(some_list)
9
---> 10 for item in generate_all():
11 ...
<ipython-input-3-b19085eab3e1> in generator(some_list)
1 def generator(some_list):
2 for i in some_list:
----> 3 raise Exception('exception happened :-)')
4 yield i
5
Exception: exception happened :-)
A jeśli używa yield from
:
def generate_all():
some_list = [1,2,3]
yield from generator(some_list)
for item in generate_all():
...
Exception Traceback (most recent call last)
<ipython-input-4-be322887df35> in <module>
8 yield from generator(some_list)
9
---> 10 for item in generate_all():
11 ...
<ipython-input-4-be322887df35> in generate_all()
6 def generate_all():
7 some_list = [1,2,3]
----> 8 yield from generator(some_list)
9
10 for item in generate_all():
<ipython-input-4-be322887df35> in generator(some_list)
1 def generator(some_list):
2 for i in some_list:
----> 3 raise Exception('exception happened :-)')
4 yield i
5
Exception: exception happened :-)
Jest to jednak kosztem wydajności. Dodatkowa warstwa generatora ma pewne narzuty. return
Będzie więc ogólnie nieco szybszy niż yield from ...
(lubfor item in ...: yield item
). W większości przypadków nie będzie to miało większego znaczenia, ponieważ cokolwiek zrobisz w generatorze, zazwyczaj dominuje w czasie wykonywania, więc dodatkowa warstwa nie będzie zauważalna.
yield
Ma jednak kilka dodatkowych zalet: nie jesteś ograniczony do jednej iteracji, możesz również z łatwością uzyskać dodatkowe przedmioty:
def generator(some_list):
for i in some_list:
yield i
def generate_all():
some_list = [1,2,3]
yield 'start'
yield from generator(some_list)
yield 'end'
for item in generate_all():
print(item)
start
1
2
3
end
W twoim przypadku operacje są dość proste i nie wiem, czy konieczne jest utworzenie wielu funkcji do tego celu, można łatwo użyć wbudowanego map
lub wyrażenia generatora:
map(do_something, get_the_list()) # map
(do_something(i) for i in get_the_list()) # generator expression
Oba powinny być identyczne (z wyjątkiem pewnych różnic, gdy zdarzają się wyjątki) do użycia. A jeśli potrzebują bardziej opisowej nazwy, nadal możesz zawinąć je w jedną funkcję.
Istnieje wiele pomocników, które zawierają bardzo popularne operacje na wbudowanych iteracjach, a dalsze można znaleźć we wbudowanym itertools
module. W takich prostych przypadkach po prostu uciekam się do nich i tylko w przypadku spraw innych niż trywialne napisz własne generatory.
Ale zakładam, że twój prawdziwy kod jest bardziej skomplikowany, więc może nie mieć zastosowania, ale pomyślałem, że nie byłby to kompletna odpowiedź bez podania alternatyw.