Los generadores tienen una evaluación perezosa, return
o yield
se comportarán de manera diferente cuando depure su código o si se produce una excepción.
Con return
cualquier excepción que ocurra en tu generator
no sabrás nada generate_all
, eso es porque cuando generator
realmente se ejecuta ya has dejado la generate_all
función. Con yield
allí tendrá generate_all
en el rastreo.
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 :-)
Y si está 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 :-)
Sin embargo, esto tiene un costo de rendimiento. La capa del generador adicional tiene algo de sobrecarga. Por return
lo tanto , generalmente será un poco más rápido que yield from ...
(o for item in ...: yield item
). En la mayoría de los casos, esto no importará mucho, porque todo lo que hagas en el generador generalmente domina el tiempo de ejecución para que la capa adicional no se note.
Sin embargo, yield
tiene algunas ventajas adicionales: no está restringido a un solo iterable, también puede generar fácilmente elementos adicionales:
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
En su caso, las operaciones son bastante simples y no sé si incluso es necesario crear múltiples funciones para esto, uno podría usar fácilmente la map
expresión incorporada o una expresión generadora:
map(do_something, get_the_list()) # map
(do_something(i) for i in get_the_list()) # generator expression
Ambos deben ser idénticos (excepto algunas diferencias cuando ocurren excepciones) para usar. Y si necesitan un nombre más descriptivo, entonces aún podría envolverlos en una función.
Hay múltiples ayudantes que envuelven operaciones muy comunes en iterables integrados y se pueden encontrar más en el itertools
módulo incorporado . En casos tan simples, simplemente recurriría a estos y solo para casos no triviales escriba sus propios generadores.
Pero supongo que su código real es más complicado, por lo que puede no ser aplicable, pero pensé que no sería una respuesta completa sin mencionar alternativas.