I generatori lo stanno valutando in modo pigro return
o yield
si comporteranno in modo diverso durante il debug del codice o se viene generata un'eccezione.
Con return
qualsiasi eccezione che accade nel tuo generator
non ne saprai nulla generate_all
, perché perché quando generator
viene realmente eseguito hai già lasciato la generate_all
funzione. Con yield
dentro avrà generate_all
nel traceback.
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 :-)
E se sta usando 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 :-)
Tuttavia, questo viene a scapito delle prestazioni. Il livello generatore aggiuntivo ha un certo sovraccarico. Quindi return
sarà generalmente un po 'più veloce di yield from ...
(o for item in ...: yield item
). Nella maggior parte dei casi questo non importa molto, perché qualsiasi cosa tu faccia nel generatore domina in genere il tempo di esecuzione in modo che il livello aggiuntivo non sia evidente.
Tuttavia yield
presenta alcuni vantaggi aggiuntivi: non sei limitato a un singolo iterabile, puoi anche facilmente produrre elementi aggiuntivi:
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
Nel tuo caso le operazioni sono abbastanza semplici e non so se sia necessario creare funzioni multiple per questo, si potrebbe semplicemente usare l' map
espressione incorporata o un generatore invece:
map(do_something, get_the_list()) # map
(do_something(i) for i in get_the_list()) # generator expression
Entrambi dovrebbero essere identici (ad eccezione di alcune differenze quando si verificano eccezioni) da utilizzare. E se hanno bisogno di un nome più descrittivo, allora potresti ancora avvolgerli in una funzione.
Esistono diversi helper che racchiudono operazioni molto comuni sugli iterabili incorporati e altri sono disponibili nel itertools
modulo integrato. In casi così semplici ricorrerei semplicemente a questi e solo per casi non banali scrivere i propri generatori.
Ma suppongo che il tuo vero codice sia più complicato, quindi potrebbe non essere applicabile, ma ho pensato che non sarebbe stata una risposta completa senza menzionare le alternative.