Autor: Diofeher
Iterables
O primeiro passo para se entender o yield é entender o que são iterables. Um objeto é iterable quando você pode percorrer seus valores usando um “for valor in objeto”.
Outra maneira de criar iterables é usando list comprehension:
123456789101112>>> lista
=
[
'd'
,
'i'
,
'o'
,
'f'
,
'e'
,
'h'
,
'e'
,
'r'
]
>>>
for
letra
in
lista:
...
letra
...
d
i
o
f
e
h
e
r
Geralmente para ser iterable, o objeto precisa ter implementado o método __iter__. Uma regra a essa exceção é a string, que não tem esse método mágico, mas que pode iterada usando seu __getitem__. Uma boa maneira de saber se um objeto é iterável ou não:
1lista
=
[letra
for
letra
in
"diofeher"
]
Se o objeto for iterável, ele é retornado. Se não, a exceção TypeError é levantada.
12345678910111213141516>>>
iter
([
1
,
2
,
3
])
<listiterator
object
at
0x1004cdc50
>
>>>
iter
(
'diofeher'
)
<iterator
object
at
0x1004cdcd0
>
>>>
iter
(
2
)
Traceback (most recent call last):
File
"<stdin>"
, line
1
,
in
<module>
TypeError:
'int'
object
is
not
iterable
>>>
iter
(
False
)
Traceback (most recent call last):
File
"<stdin>"
, line
1
,
in
<module>
TypeError:
'bool'
object
is
not
iterable
>>>
iter
(
None
)
Traceback (most recent call last):
File
"<stdin>"
, line
1
,
in
<module>
TypeError:
'NoneType'
object
is
not
iterable
Iterables são úteis porque você pode iterá-los quantas vezes quiser. Uma desvantagem do seu uso é que ele mantém TODA a lista em memória, o que pode não ser útil para grandes listas. É aí que entram os generators.
Generators
Generators são iterables, a diferença é que seus valores são lidos apenas quando é necessário. Pode-se dizer que iterables normais tem eager evalution e generators tem lazy evalution.
No exemplo acima usei o generator expression para criar o generator. Você pode percorrer pelos valores de um generator usando o método next(); Ele vai retornar cada valor do generator por vez, até chegar no final; Chegando no fim, se você tenta usar o next(), ele vai levantar uma exceção chamada StopIteration.
123456789101112131415161718>>> gerador
=
(letra
for
letra
in
"diofeher"
)
>>> gerador.
next
()
'd'
>>> gerador.
next
()
'i'
>>>
for
letra
in
gerador:
...
letra
...
o
f
e
h
e
r
>>> gerador.
next
()
Traceback (most recent call last):
File
"", line
1
,
in
StopIteration
Com generators e iterables explicados, posso chegar na dúvida inicial: yield.
Yield
Yield funciona mais ou menos como um return, com a diferença que ele retorna um generator.
Entendendo como funciona por debaixo dos panos (a parte difícil):
12345678910111213141516171819202122>>>
def
gerador():
...
for
i
in
range
(
10
):
...
yield
i
*
2
...
>>> gera
=
gerador()
>>>
gera
<generator
object
gerador at
0x1004c8960
>
>>> gera.
next
()
0
>>> gera.
next
()
2
>>>
for
i
in
gera:
...
i
...
4
6
8
10
12
14
16
18
Quando você usa a função desse jeito, o código da função não é rodado; O que é retornado é o objeto generator, para o código ser executado somente quando você chama next() ou usa um for no objeto.
Na primeira vez que a sua função for rodada, ela vai rodar do começo e parar até tocar no primeiro yield. Após tocar no primeiro yield, ela vai continuar do ponto que foi parado até achar o próximo yield. Quando não for achado um yield, a exceção StopIteration é lançada. Essa explicação fica melhor vista na função abaixo:
Uma das referências: http://stackoverflow.com/a/231855/914874
123456789101112131415161718>>>
def
test():
...
yield
1
...
for
i
in
range
(
3
):
...
yield
i
...
>>> testing
=
test()
>>> testing.
next
()
1
>>> testing.
next
()
0
>>> testing.
next
()
1
>>> testing.
next
()
2
>>> testing.
next
()
Traceback (most recent call last):
File
"<stdin>"
, line
1
,
in
<module>
StopIteration
Comentários