Os geradores estão avaliando preguiçosamente return
ou yield
se comportarão de maneira diferente quando você estiver depurando seu código ou se uma exceção for lançada.
Com return
qualquer exceção que você generator
não saiba generate_all
, é porque quando generator
é realmente executado você já deixou a generate_all
função. Com yield
lá vai ter generate_all
no 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 estiver 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 :-)
No entanto, isso tem um custo de desempenho. A camada adicional do gerador possui alguma sobrecarga. Então return
, geralmente será um pouco mais rápido que yield from ...
(ou for item in ...: yield item
). Na maioria dos casos, isso não importa muito, porque o que você faz no gerador normalmente domina o tempo de execução, para que a camada adicional não seja perceptível.
No entanto, yield
possui algumas vantagens adicionais: você não está restrito a uma única iterável, mas também pode gerar itens adicionais com facilidade:
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
No seu caso, as operações são bastante simples e não sei se é necessário criar várias funções para isso, seria possível usar facilmente a map
expressão incorporada ou de gerador:
map(do_something, get_the_list()) # map
(do_something(i) for i in get_the_list()) # generator expression
Ambos devem ser idênticos (exceto por algumas diferenças quando ocorrerem exceções) a serem usados. E se eles precisarem de um nome mais descritivo, você ainda poderá agrupá-los em uma função.
Existem vários auxiliares que envolvem operações muito comuns nos iterables internos e outros podem ser encontrados no itertools
módulo interno. Em casos tão simples, eu simplesmente recorreria a esses e somente para casos não triviais escreva seus próprios geradores.
Mas suponho que seu código real seja mais complicado, pelo que pode não ser aplicável, mas achei que não seria uma resposta completa sem mencionar alternativas.