O que faz a palavra-chave" yield"?
Qual é a utilidade da palavra-chave yield
em Python? O que faz?
def _get_child_candidates(self, distance, min_dist, max_dist):
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
E este é o chamador.
result, candidates = [], [self]
while candidates:
node = candidates.pop()
distance = node._get_dist(obj)
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
o que acontece quando o método _get_child_candidates
é chamado?
A lista foi devolvida? Um único elemento? Chama-se outra vez? Quando é que as chamadas posteriores vão parar?
1. O código vem de Jochen Schulz (jrschulz), que fez uma grande biblioteca Python para espaços métricos. Este é o ligação à fonte completa: Módulo Mspace .
30 answers
Para entender o que yield
faz, você deve entender o que geradores são. E antes que os geradores cheguem.
Iterables
Quando se cria uma lista, pode-se ler os seus itens um a um. Ler os seus itens um a um chama-se iteração:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
é um iterável . Quando você usa uma compreensão de lista, você cria uma lista, e assim uma iterável:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
Tudo o que se pode usar em "for... in...
" é iterável.; lists
, strings
, ficheiros...
Estes iterables são úteis porque você pode lê-los o quanto quiser, mas você armazena todos os valores na memória e isso não é sempre o que você quer quando você tem um monte de valores.
Geradores
Os geradores são iteradores, uma espécie de iterável. Os geradores não armazenam todos os valores na memória, geram os valores na altura:>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
É a mesma coisa excepto que usaste ()
em vez de []
. Mas, você Não pode executar for i in mygenerator
uma segunda vez, uma vez que os geradores só podem ser usados uma vez: eles calculam 0, em seguida, esqueça sobre isso e calcular 1, e terminar o cálculo 4, um por um.
Rendimento
yield
é uma palavra-chave que é usada como return
, exceto que a função irá retornar um gerador.
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
Aqui está um exemplo inútil, mas é útil quando se sabe que a função irá devolver um enorme conjunto de valores que só terá de ler. quando.
Para mestre yield
, Você deve entender que quando você chama a função, o código que você escreveu no corpo da função não funciona. a função só devolve o objecto gerador, isto é um pouco complicado: -)
Agora a parte difícil:
A primeira vez que o for
chama o objecto gerador criado a partir da sua função, ele irá executar o código na sua função a partir do começando até atingir yield
, depois devolve o primeiro valor do laço. Então, cada outra chamada irá executar o loop que você escreveu na função mais uma vez, e retornar o próximo valor, até que não há nenhum valor para retornar.
O gerador é considerado vazio uma vez que a função funciona, mas não atinge yield
mais. Pode ser porque o ciclo tinha chegado a um fim, ou porque você não satisfaz mais um "if/else"
.
O teu código explicado
Gerador:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
Ouvinte:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidates list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
Este código contém várias partes inteligentes:
O loop itera em uma lista, mas a lista se expande enquanto o loop está sendo iterado :-) é uma maneira concisa de percorrer todos esses dados aninhados, mesmo que seja um pouco perigoso, uma vez que você pode acabar com um loop infinito. Neste caso,
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
esgota todos os valores do gerador, maswhile
continua a criar novos objectos geradores que irão produzir valores diferentes dos anteriores, uma vez que não é aplicado no mesmo nó.O método
extend()
é um método de objetos de lista que espera um iterável e adiciona seus valores à lista.
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
Mas no seu código tem um gerador, o que é bom porque ...
-
Não precisas de ler os valores duas vezes.
Pode ter muitos filhos e não os quer todos armazenados em memoria.
E funciona porque o Python não se importa se o argumento de um método é uma lista ou não. O Python espera iterables para que funcione com strings, listas, tuplas e geradores! Isto é chamado de datilografia de pato e é uma das razões Por Que Python é tão legal. Mas esta é outra história, para outra pergunta...
Pode parar aqui, ou ler um pouco para ver um uso avançado de um gerador:
Controlar um gerador esgotamento
>>> class Bank(): # Let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
Nota: para o Python 3, Utilizar print(corner_street_atm.__next__())
ou print(next(corner_street_atm))
Então apenas import itertools
.
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
Compreender os mecanismos internos da iteração
Iteração é um processo que implica iterables (implementando o método __iter__()
) e iteradores (implementando o método __next__()
).
Iterables são todos os objectos a quem se pode arranjar um iterador. Os iteradores são objectos que te deixam entrar nos iterables.
Há mais sobre isso neste artigo sobre como for
loops funcionam.
Atalho para Grokking yield
Quando vir uma função com yield
declarações, aplique este truque fácil para entender o que vai acontecer:
- inserir uma linha
result = []
no início da função. - substituir cada
yield expr
porresult.append(expr)
. - inserir uma linha
return result
na parte inferior da função.
Chega de declarações! Lê e descobre o código.
- Compare a função com a definição original.
yield
é significativamente diferente do que acontece na abordagem baseada na lista. Em muitos casos, o yield approach será muito mais eficiente em memória e mais rápido também. Em outros casos, este truque vai colocá-lo preso em um loop infinito, mesmo que a função original funciona muito bem. Leia mais para saber mais...
Não confundas os teus Iterables, iteradores e geradores.
Primeiro, o iterador. protocolo - quando escrever
for x in mylist:
...loop body...
O Python executa as seguintes duas etapas:
-
Arranja um iterador para
mylist
:Call
Este é o passo que a maioria das pessoas se esquece de te contar.iter(mylist)
- > isto devolve um objecto com um métodonext()
(ou__next__()
no Python 3). -
Usa o iterador para repetir os itens:
Continue chamando o método
next()
no iterador retornado do Passo 1. O valor de retorno denext()
é atribuído ax
e o corpo do laço é executado. Se uma exceçãoStopIteration
é levantada a partir de dentronext()
, significa que não há mais valores no iterador e o laço é saído.
A verdade é que o Python executa os dois passos acima sempre que quiser fazer um loop sobre o conteúdo de um objecto - por isso pode ser um loop for, mas também pode ser um código como otherlist.extend(mylist)
(onde otherlist
é uma lista em Python).
Aqui mylist
é um iterável porque implementa o protocolo iterator. Numa classe definida pelo utilizador, pode implementar o método __iter__()
para tornar as instâncias da sua classe iteráveis. Este método deve devolver um iterador. Um iterador é um objeto com um método next()
. É possível implementar ambos __iter__()
e next()
na mesma classe, e ter __iter__()
de volta self
. Isto vai funcionar para casos simples, mas não quando você quer dois iteradores looping sobre o mesmo objeto ao mesmo tempo.
- listas incorporadas, dicionários, tuplas, conjuntos, ficheiros.
- classes definidas pelo utilizador que implementam
__iter__()
.
Geradores.
Note que um laço for
não sabe com que tipo de objecto está a lidar - apenas segue o protocolo iterator, e fica feliz por obter item após item como chama next()
. As listas incorporadas devolvem os seus itens um a um, os dicionários devolvem as chaves um a um, os ficheiros devolvem a linhas uma a uma, etc. E os geradores regressam... bem, é aí que yield
entra:
def f123():
yield 1
yield 2
yield 3
for item in f123():
print item
Em Vez de yield
declarações, se você tivesse três return
declarações em f123()
apenas o primeiro seria executado, e a função de saída. Mas f123()
não é uma função comum. Quando f123()
é chamado, ele não devolve nenhum dos valores nas declarações de rendimento! Devolve um objecto gerador. Além disso, a função realmente não sai - ela vai para um estado suspenso. Quando a for
o ciclo tenta fazer um ciclo sobre o objecto gerador, a função retoma do seu estado suspenso na linha seguinte após o {[[2]} de onde voltou anteriormente, executa a próxima linha de código, neste caso uma instrução yield
, e devolve-a como o próximo item. Isto acontece até que a função saia, em que ponto o gerador levanta StopIteration
, e o laço sai.
__iter__()
e next()
métodos para manter o ciclo feliz. No outro extremo, no entanto, ele executa a função apenas o suficiente para obter o próximo valor fora dele, e coloca-o de volta no modo suspenso.
Para Quê Usar Geradores?
Normalmente você pode escrever um código que não usa geradores, mas implementa a mesma lógica. Uma opção é usar a lista temporária 'trick' que mencionei antes. Isso não vai funcionar em todos os casos, por exemplo, se você tem laços infinitos, ou pode fazer uso ineficiente da memória quando tens uma lista muito longa. A outra abordagem é implementar uma nova classe iterável SomethingIter
que mantém os membros do estado em instância e executa o próximo passo lógico no seu método next()
(ou __next__()
em Python 3). Dependendo da lógica, o código dentro do método next()
pode acabar parecendo muito complexo e ser propenso a bugs. Aqui os geradores fornecem uma solução limpa e fácil.
Um iterador é apenas um termo de sonoridade extravagante para um objeto que tem um próximo() método. Então uma função yield-ed acaba sendo algo assim:
Versão Original:
def some_function():
for i in xrange(4):
yield i
for i in some_function():
print i
Isto é basicamente o que o interpretador Python faz com o código acima:
class it:
def __init__(self):
# Start at -1 so that we get 0 when we add 1 below.
self.count = -1
# The __iter__ method will be called once by the 'for' loop.
# The rest of the magic happens on the object returned by this method.
# In this case it is the object itself.
def __iter__(self):
return self
# The next method will be called repeatedly by the 'for' loop
# until it raises StopIteration.
def next(self):
self.count += 1
if self.count < 4:
return self.count
else:
# A StopIteration exception is raised
# to signal that the iterator is done.
# This is caught implicitly by the 'for' loop.
raise StopIteration
def some_func():
return it()
for i in some_func():
print i
Para mais informações sobre o que está a acontecer nos bastidores, o loop pode ser reescrito para isto:
iterator = some_func()
try:
while 1:
print iterator.next()
except StopIteration:
pass
Isso faz mais sentido ou só te confunde mais? :)
Devo notar que este é uma simplificação excessiva para fins ilustrativos. :)
A palavra-chave é reduzida a dois factos simples:
- Se o compilador detectar a palavra-chave
yield
em qualquer lugar dentro de uma função, essa função já não retorna através da instruçãoreturn
. em vez disso, IT immediately returns a lazy "pending list" object called a generator
Um gerador é iterável. O que é um iterável? É qualquer coisa como um
list
ou set
ou {[12] } ou dict-view, com um protocolo incorporado para visitar cada elemento numa determinada ordem .
Em poucas palavras: um gerador é uma lista preguiçosa, incrementalmente pendente, e yield
as instruções permitem-lhe usar a notação da função para programar os valores da lista o gerador deve cuspir gradualmente.
generator = myYieldingFunction(...)
x = list(generator)
generator
v
[x[0], ..., ???]
generator
v
[x[0], x[1], ..., ???]
generator
v
[x[0], x[1], x[2], ..., ???]
StopIteration exception
[x[0], x[1], x[2]] done
list==[x[0], x[1], x[2]]
Exemplo
Vamos definir uma função como a do Python. ChamandomakeRange(n)
devolve um gerador:
def makeRange(n):
# return 0,1,2,...,n-1
i = 0
while i < n:
yield i
i += 1
>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>
Para forçar o gerador para devolver imediatamente os seus valores pendentes, você pode passá-lo em list()
(assim como você poderia qualquer iterável):
>>> list(makeRange(5))
[0, 1, 2, 3, 4]
Comparando exemplo com "apenas retornando uma lista"
O exemplo acima pode ser pensado apenas como criando uma lista à qual você adiciona e retorna:
# list-version # # generator-version
def makeRange(n): # def makeRange(n):
"""return [0,1,2,...,n-1]""" #~ """return 0,1,2,...,n-1"""
TO_RETURN = [] #>
i = 0 # i = 0
while i < n: # while i < n:
TO_RETURN += [i] #~ yield i
i += 1 # i += 1 ## indented
return TO_RETURN #>
>>> makeRange(5)
[0, 1, 2, 3, 4]
No entanto, há uma grande diferença; ver a última secção.
Como utilizar geradores
Um iterável é a última parte de uma compreensão da lista, e todos os geradores são iteráveis, por isso são frequentemente usados assim.
# _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]
Para obter uma melhor sensação para os geradores, você pode brincar com o módulo itertools
(certifique-se de usar chain.from_iterable
em vez de chain
quando necessário). Por exemplo, você pode até usar geradores para implementar listas preguiçosas infinitamente longas como itertools.count()
. Você pode implementar seu próprio def enumerate(iterable): zip(count(), iterable)
, ou alternativamente fazê-lo com a palavra-chave yield
em um ciclo de tempo.
Por favor, a ter em conta: os geradores podem ser utilizados para muitas mais coisas., tais comoimplementar coroutinas ou programação não-determinística ou outras coisas elegantes. No entanto, o ponto de vista" listas preguiçosas " que eu apresento aqui é o uso mais comum que você vai encontrar.
Nos bastidores É assim que funciona o" protocolo de iteração Python". Isto é, o que está acontecendo quando você faz
list(makeRange(5))
. Isto é o que eu descrevi anteriormente como uma "lista preguiçosa e incremental".
>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
A função embutida next()
apenas chama os objectos .next()
função, que é uma parte do" protocolo de iteração " e é encontrado em todos os iteradores. Você pode usar manualmente a função next()
(e outras partes do protocolo de iteração) para implementar coisas extravagantes, geralmente à custa da legibilidade, então tente evitar fazer isso...
Minutiae
Normalmente, a maioria das pessoas não se importaria com as seguintes distinções e provavelmente quer parar de ler aqui.Em Python-speak, um iterable é qualquer objecto que " compreenda o conceito de um for-loop " como uma lista [1,2,3]
, e um iterator é uma instância específica do for-loop solicitado como [1,2,3].__iter__()
. Um gerador é exatamente o mesmo que qualquer iterador, exceto pela forma como foi escrito (com sintaxe de função).
Assim, no caso improvável que estás a falhar em fazer algo assim...
> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]
... então lembre-se que um gerador é um iterador ; isto é, é um uso único. Se você quiser reutilizá-lo, você deve ligar para myRange(...)
novamente. Se você precisar usar o resultado duas vezes, converter o resultado para uma lista e armazená-lo em uma variável x = list(myRange(5))
. Aqueles que absolutamente precisam clonar um gerador (por exemplo, que estão fazendo terrivelmente hackish metaprogramming) podem usar itertools.tee
se absolutamente necessário, uma vez que o a proposta de normas iterator Python copiável foi adiada.
O Que Faz A Palavra-chave
yield
em Python?
Resumo Das Respostas
- Uma função com
yield
, quando chamado, retorna uma Gerador.
Os geradores são iteradores porque implementam o protocolo iterador, para que possas entreter-te com eles.
- um gerador também pode ser enviado informação , tornando-se conceptualmente um coroutine . Em Python 3, você pode delegar de um gerador para outro em ambas as direções com
- (Appendix critiques a couple of answers, including the top one, and discusses the use of
return
in a generator.)
yield from
.
Geradores:
yield
é apenas legal dentro de uma definição de função, e a inclusão de yield
em uma definição de função faz com que retorne um gerador.
A a ideia para geradores vem de outras línguas (Ver nota 1) com diferentes implementações. Nos geradores de Python, a execução do código é congelada no ponto do rendimento. Quando o gerador é chamado (métodos são discutidos abaixo) a execução recomeça e então congela no próximo rendimento.
yield
fornece um
modo fácil de implementação do protocolo iterator ([90]}, definido pelos dois métodos seguintes::
__iter__
e next
(Python 2) ou __next__
(Python 3). Quer desses métodos
faça de um objecto um iterador que possa digitar-verificar com a base abstracta Iterator
Classe do módulo collections
>>> def func():
... yield 'I am'
... yield 'a generator!'
...
>>> type(func) # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen) # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__') # that's an iterable
True
>>> hasattr(gen, 'next') # and with .next (.__next__ in Python 3)
True # implements the iterator protocol.
O tipo gerador é um subtipo de iterador:
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True
E se necessário, podemos digitar assim:
>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True
Uma característica de um Iterator
é que uma vez esgotado {[[90]}, você não pode reutilizá-lo ou reiniciá-lo:
>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]
Você terá que fazer outro se você quiser usar sua funcionalidade novamente (Ver nota de rodapé) 2):
>>> list(func())
['I am', 'a generator!']
Pode-se produzir dados programaticamente, por exemplo:
def func(an_iterable):
for item in an_iterable:
yield item
O gerador simples acima também é equivalente ao Python 3.3 (e não está disponível no Python 2), você pode usar yield from
:
def func(an_iterable):
yield from an_iterable
No entanto, yield from
também permite a delegação em subgéneros.,
que será explicado na secção seguinte sobre delegação cooperativa com sub-coroutinas.
Coroutinas:
yield
forma uma expressão que permite dados a enviar para o gerador (ver Nota 3)
Aqui está um exemplo, tome nota da variável received
, que apontará para os dados que são enviados para o gerador:
def bank_account(deposited, interest_rate):
while True:
calculated_interest = interest_rate * deposited
received = yield calculated_interest
if received:
deposited += received
>>> my_account = bank_account(1000, .05)
Primeiro, temos de colocar o gerador em fila com a função builtin., next
. Vai.
chame o método apropriado next
ou __next__
, dependendo da versão de
Python que está a usar:
>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0
E agora podemos enviar dados para o gerador. ([162]}enviar None
é
o mesmo que chamar next
.) :
>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5
([165]) delegação cooperativa na sub-Coroutine com yield from
Agora, lembre-se que yield from
está disponível no Python 3. Isto permite-nos delegar
coroutinas a uma subcoroutina:
def money_manager(expected_rate):
under_management = yield # must receive deposited value
while True:
try:
additional_investment = yield expected_rate * under_management
if additional_investment:
under_management += additional_investment
except GeneratorExit:
'''TODO: write function to send unclaimed funds to state'''
finally:
'''TODO: write function to mail tax info to client'''
def investment_account(deposited, manager):
'''very simple model of an investment account that delegates to a manager'''
next(manager) # must queue up manager
manager.send(deposited)
while True:
try:
yield from manager
except GeneratorExit:
return manager.close()
E agora podemos delegar a funcionalidade a um sub-gerador e pode ser usado.
por um gerador igual ao acima:
>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6
Você pode ler mais sobre a semântica precisa de yield from
em PEP 380.
Outros métodos: atira.
O método close
aumenta GeneratorExit
no ponto em que a função
a execução foi congelada. Isto também será chamado por __del__
assim você
pode colocar qualquer código de limpeza onde você lidar com o GeneratorExit
:
>>> my_account.close()
Também pode abrir uma excepção que pode ser tratada no gerador.
ou propagado de volta ao utilizador:
>>> import sys
>>> try:
... raise ValueError
... except:
... my_manager.throw(*sys.exc_info())
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
File "<stdin>", line 2, in <module>
ValueError
Conclusão
Acho que já abordei todos os aspectos da seguinte pergunta:Acontece queO que faz a palavra-chave
yield
em Python?
yield
faz muito. Tenho a certeza que posso acrescentar ainda mais
exemplos completos disso. Se quiser mais ou tiver alguma crítica construtiva, avise-me comentando
abaixo.
Apêndice:
Crítica da resposta superior / aceite * *
- está confuso sobre o que torna um iterável , apenas usando uma lista como exemplo. Veja minhas referências acima, mas em resumo: um iterável tem um método
__iter__
retornando um Iterator Um iterator fornece um método.next
(Python 2 ou.__next__
(Python 3), que é implicitamente chamado porfor
loops até que ele levanteStopIteration
, e uma vez que ele o faça, ele continuará a fazê-lo.
Então usa uma expressão do gerador para descrever o que é um gerador. Uma vez que um gerador é simplesmente uma maneira conveniente de criar um iterador , ele só confunde a matéria, e ainda não chegamos à parte - em controlo a exaustão do gerador {[80] } ele chama o método
.next
, quando em vez disso ele deve usar a função de Construção,next
. Seria uma camada apropriada de indireção, porque seu código não funciona no Python 3.
Itertools? Isto não era relevante para o que - nenhuma discussão dos métodos que
yield
fornece juntamente com a nova funcionalidadeyield from
no Python 3. a resposta superior / aceite é uma resposta muito incompleta.
yield
.
yield
faz.
A gramática permite actualmente qualquer expressão na compreensão de uma lista.
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist
Uma vez que o rendimento é uma expressão, tem sido considerado por alguns como interessante usá - lo em compreensões ou expressão geradora-apesar de não citar nenhum caso de uso particularmente bom.
Os programadores do CPython estão a discutir depreciar o seu subsídio. Aqui está um post relevante do correio lista:
Além disso, há uma emissão pendente (10544) que parece estar apontando na direção deste never sendo uma boa ideia (o PyPy, uma implementação em Python escrita em Python, já está levantando avisos de sintaxe.)Em 30 de janeiro de 2017, às 19:05, Brett Cannon escreveu:
Concordo que é um lugar sensato para acabarmos. codigo confiar no comportamento actual é realmente demasiado inteligente para ser passivel. Em termos de chegar lá, é provável que queiramos:On Sun, 29 Jan 2017 at 16: 39 Craig Rodrigues wrote:
Não me importo com nenhuma das abordagens. Deixando as coisas como estão no Python 3 não é bom, IMHO.O meu voto é que seja um erro Sintaxerror, já que não está a conseguir o que espera de mim. sintaxe.SyntaxWarning ou Depreciationwarning em 3.7 Aviso Py3k em 2.7.x SyntaxError em 3, 8
Saúde, Nick.-- Nick Coghlan / ncoghlan at gmail.com / Brisbane, Austrália
Conclusão, até que os programadores do CPython nos digam o contrário: Não coloque yield
numa expressão ou compreensão geradora.
A declaração return
num gerador
Em Python 2:
Numa função geradora, a declaração
return
não é permitida. incluir umexpression_list
. Nesse contexto, um barereturn
indica que o gerador está feito e fará com queStopIteration
seja levantado.
An expression_list
é basicamente qualquer número de expressões separadas por vírgulas - essencialmente, no Python 2, você pode parar o gerador com {[[18]}, mas você não pode devolver um valor.
Em Python 3:
Numa função do gerador, a declaração
return
indica que o gerador está feito e fará com queStopIteration
seja elevado. O o valor devolvido (se existir) é usado como argumento para construirStopIteration
e torna-se o atributoStopIteration.value
.
Notas de rodapé
as línguas CLU, Sather e Icon foram referenciadas na proposta para introduzir o conceito de geradores para Python. A ideia geral é: que uma função pode manter o estado interno e produzir intermediário pontos de dados a pedido do utilizador. Isto prometeu ser superior no desempenho. outras abordagens, incluindo Python threading , que nem sequer está disponível em alguns sistemas.
isto significa, por exemplo, que os objetos
xrange
(range
no Python 3) não sãoIterator
s, mesmo que sejam iteráveis, porque podem ser reutilizados. Como listas, seus métodos__iter__
retornam objetos iteradores.yield
foi originalmente introduzido como uma declaração, o que significa que só pode aparecer no início de uma linha num bloco de código. Agorayield
cria um rendimento expressao. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Esta alteração foi proposta para permitir que um utilizador enviasse dados para o gerador tal como talvez alguém a Receba. Para enviar dados, deve-se ser capaz de atribuí-lo a algo, e para isso, uma declaração não vai resultar.
yield
é como return
- devolve o que lhe disser (como um gerador). A diferença é que da próxima vez que você ligar para o gerador, a execução começa da última chamada para a declaração yield
. Ao contrário do return, a estrutura da pilha não é limpa quando ocorre um rendimento, no entanto o controle é transferido de volta para o chamador, de modo que seu estado irá retomar a próxima vez que a função.
No caso do seu código, a função {[3] } está a agir como um iterador para que quando você Extenda sua lista, adiciona um elemento de cada vez para a nova lista.
list.extend
chama um iterador até estar exausto. No caso da amostra de código que você postou, seria muito mais claro apenas retornar uma tupla e adicioná-la à lista.
def fib():
last, cur = 0, 1
while True:
yield cur
last, cur = cur, last + cur
Então posso usá-lo noutro código como este:
for f in fib():
if some_condition: break
coolfuncs(f);
Realmente ajuda a simplificar alguns problemas, e torna algumas coisas mais fáceis de trabalhar.
Para aqueles que preferem um exemplo de trabalho mínimo, meditem nesta sessão interactiva em Python :
>>> def f():
... yield 1
... yield 2
... yield 3
...
>>> g = f()
>>> for i in g:
... print i
...
1
2
3
>>> for i in g:
... print i
...
>>> # Note that this time nothing was printed
def get_odd_numbers(i):
return range(1, i, 2)
def yield_odd_numbers(i):
for x in range(1, i, 2):
yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5
Como podem ver, no primeiro caso, o foo guarda a lista inteira em memória de uma só vez. Não é grande coisa para uma lista com 5 elementos, mas e se você quiser uma lista de 5 milhões? Não só isso é um enorme Devorador de memória, também custa muito tempo para construir no momento em que a função é chamada. No segundo caso, o bar dá-te um gerador. Um gerador é iterável--o que significa que você pode usá-lo em um laço for, etc, mas cada valor só pode ser acedido uma vez. Todos os valores também não são armazenados na memória ao mesmo tempo; o objeto gerador "lembra" onde estava no looping da última vez que você chamou-desta forma, se você está usando um iterável para (digamos) contar até 50 bilhões, você não tem que contar até 50 bilhões de uma vez e armazenar os 50 bilhões de números para contar. Mais uma vez, este é um exemplo bastante inventado, você provavelmente usaria itertools se você realmente quisesse contar até 50 bilhões. :)
Isto é o mais simples. caso de utilização de geradores. Como você disse, ele pode ser usado para escrever permutações eficientes, usando yield para empurrar as coisas através da pilha de chamadas em vez de usar algum tipo de variável de pilha. Geradores também podem ser usados para árvores especializadas transversais, e todos os tipos de outras coisas.
Existe um artigo da IBM {[3] } que o explica razoavelmente bem (para Python), tanto quanto consigo ver.
A ideia chave é que o compilador/intérprete / o que quer que faça algum truque para que, no que diz respeito ao chamador, eles possam continuar a ligar a seguir () e ele vai continuar a devolver os valores - como se o método do gerador estivesse em pausa. Agora obviamente você não pode realmente "pausar" um método, então o compilador constrói uma máquina de Estado para que você se lembre onde você está atualmente e como as variáveis locais etc parecem. Isto é muito mais fácil do que escrever um iterador.
TL; DR
Em vez disto:
def squares_list(n):
the_list = [] # Replace
for x in range(n):
y = x * x
the_list.append(y) # these
return the_list # lines
Faz isto:
def squares_the_yield_way(n):
for x in range(n):
y = x * x
yield y # with this one.
Sempre que você se encontra construindo uma lista do zero, yield
Cada peça em vez.
yield
é uma maneira doce de dizer
Construir uma série de coisas
O mesmo comportamento:
>>> for square in squares_list(4):
... print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
... print(square)
...
0
1
4
9
Comportamento diferente:
O rendimento é de passagem única : só se pode iterar através quando. Quando uma função tem um rendimento nela, chamamos-lhe função gerador . E um iterador é o que volta. Isso é Revelador. Perdemos a conveniência de um recipiente, mas ganhamos o poder de uma série arbitrariamente longa.
O rendimento é preguiçoso , atrasa a computação. Uma função com um rendimento nela realmente não executa nada quando você a chama. O objecto iterador que devolve usa magia para manter o contexto interno da função. Cada vez que você chama next()
no iterador (isso acontece em um for-loop) execução polegadas para frente para o próximo rendimento. (return
aumenta StopIteration
e termina a série.)
O rendimento é versátil . Pode fazer laços infinitos:
>>> def squares_all_of_them():
... x = 0
... while True:
... yield x * x
... x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
... print(next(squares))
...
0
1
4
9
Se precisar de múltiplos passes e a série não for muito longa, basta ligar list()
para ele:
>>> list(squares_the_yield_way(4))
[0, 1, 4, 9]
Escolha brilhante da palavra yield
porque ambos os significados aplicam-se:
Yield - produzir ou fornecer (como na agricultura)
...fornecer os dados seguintes da série.
Yield - ceder ou renunciar (como no poder político)
...renuncia à execução da CPU até o iterator avançar.
A instrução yield
em Python devolve um gerador. Um gerador em Python é uma função que retorna continuações (e, especificamente, um tipo de co-rotina, mas continuações representam o mais mecanismo geral para entender o que está acontecendo).
Continuações em a teoria das linguagens de programação é um tipo muito mais fundamental de computação, mas elas não são frequentemente usadas, porque elas são extremamente difíceis de raciocinar e também muito difíceis de implementar. Mas a idéia do que é uma continuação, é simples: é o estado de uma computação que ainda não terminou. Neste estado, os valores atuais das variáveis, as operações que ainda não foram realizadas, e assim por diante, são salvos. Então, em algum momento mais tarde no programa a continuação pode ser invocados, de modo que as variáveis do programa são repostas a esse estado e as operações que foram salvas são realizadas.
As continuações, desta forma mais geral, podem ser implementadas de duas maneiras. Da maneira call/cc
, a pilha do programa é literalmente guardada e então, quando a continuação é invocada, a pilha é restaurada.
No estilo de passagem contínua (CPS), as continuações são apenas funções normais (apenas em linguagens onde as funções são de primeira classe) que o programador explicitamente gerencia e passa para sub-rotinas. Neste estilo, o estado do programa é representado por encerramentos (e as variáveis que por acaso são codificadas neles) ao invés de variáveis que residem em algum lugar na pilha. Funções que gerenciam o fluxo de controle aceitam continuação como argumentos (em algumas variações de CPS, funções podem aceitar múltiplas continuações) e manipulam o fluxo de controle invocando-os simplesmente chamando-os e retornando depois. Um exemplo muito simples de continuação o estilo de passagem é o seguinte:
def save_file(filename):
def write_file_continuation():
write_stuff_to_file(filename)
check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)
Neste (muito simplista) exemplo, o programador salva a operação de realmente gravar o arquivo em uma continuação (que potencialmente pode ser uma operação muito complexa, com muitos detalhes para escrever) e, em seguida, passa-continuação (que eu.e, como um de primeira classe encerramento) para outro operador que faz um pouco mais de processamento, e, em seguida, chama-a, se necessário. (Eu uso este padrão de design muito na programação GUI real, ou porque ele me poupa linhas de código ou, mais importante, para gerenciar o fluxo de controle após o desencadeamento de eventos GUI.)
O resto deste post irá, sem perda de generalidade, conceituar continuações como CPS, porque é muito mais fácil de entender e ler.
def f():
while True:
yield 4
Este é claramente um iterável razoável cujo comportamento é bem definido -- cada vez que o gerador itera sobre ele, ele retorna 4 (e faz isso para sempre). Mas não é provavelmente o tipo prototípico de iterável que vem à mente quando se pensa em iteradores (isto é, for x in collection: do_something(x)
). Presente exemplo ilustra a potência dos geradores: se alguma coisa é um iterador, um gerador pode salvar o estado de sua iteração.
Para reiterar: continuações podem salvar o estado da pilha de um programa e geradores podem salvar o estado de iteração. Isso significa que as continuações são muito mais poderosas do que os geradores, mas também que os geradores são muito, muito mais fácil. Eles são mais fáceis para o designer de linguagem para implementar, e eles são mais fáceis para o programador para usar (se você tem algum tempo para queime, tente ler e entender esta página sobre continuações e ligue / cc ).
Mas você poderia facilmente implementar (e conceitualizar) geradores como um caso simples e específico de continuação passando estilo:Sempre que yield
é chamado, ele diz à função para retornar uma continuação. Quando a função é chamada novamente, ela começa de onde quer que ela parou. Assim, Em pseudo-pseudocode (isto é, não pseudocode, mas não código) o método do gerador next
é basicamente como
class Generator():
def __init__(self,iterable,generatorfun):
self.next_continuation = lambda:generatorfun(iterable)
def next(self):
value, next_continuation = self.next_continuation()
self.next_continuation = next_continuation
return value
Onde a palavra-chave {[[4]} é na verdade açúcar sintático para a função gerador real, basicamente algo como:
def generatorfun(iterable):
if len(iterable) == 0:
raise StopIteration
else:
return (iterable[0], lambda:generatorfun(iterable[1:]))
Lembre-se que isto é apenas pseudocode e a implementação real de geradores em Python é mais complexa. Mas como um exercício para entender o que está acontecendo, tente usar o estilo de passagem de continuação para implementar objetos geradores sem o uso da palavra-chave yield
.
- Eu ligo-te e digo-te que quero uma sequência de números que seja produzida de uma forma específica, e depois digo-te o que é que o é o algoritmo.
- algum dia depois, eu digo-te,"OK, prepara-te para me dizeres A sequência de números".
Este passo corresponde a chamar a função gerador que retorna um objeto gerador.Note que ainda não me diz números, pegue no seu papel e lápis.
Eu pergunto-te, "diz-me o próximo número", e tu dizes-me o primeiro número.; depois disso, espera que te Peça o próximo número. O teu trabalho é lembrar-te Onde estavas, quais os números que já disseste, e qual é o próximo número. Não quero saber dos detalhes. - ... repetir o passo anterior, até... Eventualmente, podes chegar ao fim. Não me dizes um número, só gritas: "aguenta os cavalos! Estou farto! Acabaram-se os números!"
Este passo corresponde a
def
ining a função gerador, ou seja, a função que contém um yield
.
Este passo corresponde à chamada
.next()
no objecto gerador.
Este passo corresponde ao objecto gerador que termina a sua tarefa e levanta uma excepção
StopIteration
a função do gerador não precisa de levantar a excepção. É levantado automaticamente quando a função termina ou emite um return
.
Isto é o que um gerador faz (uma função que contém um yield
); ele começa a executar, pára sempre que faz um yield
, e quando lhe é pedido um valor .next()
continua a partir do ponto em que foi o último. Ele se encaixa perfeitamente pelo design com o protocolo iterator de Python, que descreve como solicitar sequencialmente valores.
O utilizador mais famoso do protocolo iterator é o comando for
em Python. Então, sempre que você faz um:
for item in sequence:
Não importa se sequence
é uma lista, uma cadeia de caracteres, um dicionário ou um gerador {[[47]} como descrito acima; o resultado é o mesmo: você lê itens de uma sequência um por um.
Note que def
ining a function which contains a yield
keyword is not the only way to create a generator; it is just a maneira mais fácil de criar um.
Para informações mais precisas, leia sobretipos iteradores , a declaração de rendimentoe geradoresna documentação em Python.
Apesar de muitas respostas mostrarem por que você usaria um yield
para criar um gerador, existem mais usos para yield
. É muito fácil fazer um coroutine, que permite a passagem de informações entre dois blocos de código. Não vou repetir nenhum dos bons exemplos que já foram dados sobre o uso de yield
para criar um gerador.
Para ajudar a compreender o que um yield
faz no seguinte código, pode usar o seu dedo para localizar o ciclo através de qualquer código que tenha UM yield
. Sempre seu dedo bate no yield
, você tem que esperar por um next
ou um send
para ser inserido. Quando um next
é chamado, você rastreia através do Código até que você carrega no yield
... o código à direita do yield
é avaliado e devolvido ao ouvinte ... então você espera. Quando next
é chamado novamente, você executa outro loop através do Código. No entanto, você vai notar que em um coroutine, yield
também pode ser usado com um send
... que irá enviar um valor do chamador para a função de rendimento. Se for dado, então yield
recebe o valor enviado, e cospe-o para fora do lado esquerdo ... em seguida, o traço através do Código progride até você atingir o yield
novamente (retornando o valor no final, como se next
fosse chamado).
Por exemplo:
>>> def coroutine():
... i = -1
... while True:
... i += 1
... val = (yield i)
... print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
Existe outro yield
Uso e significado (desde o Python 3.3):
yield from <expr>
De PEP 380 -- sintaxe para delegar a um Subgenerador:
É proposta uma sintaxe para um gerador delegar parte das suas operações a outro gerador. Isto permite que uma seção de código contendo "rendimento" seja contabilizada e colocada em outro gerador. Além disso, o subgenerador é permitido retornar com um valor, e o valor é disponibilizado para a delegação gerador.
A nova sintaxe também abre algumas oportunidades de otimização quando um gerador rende valores produzidos por outro.
Além disso Este irá introduzir (desde o Python 3.5):
async def new_coroutine(data):
...
await blocking_action()
Para evitar que as coroutinas sejam confundidas com um gerador regular (hoje yield
é usado em ambos).
Eu ia postar "leia a Página 19 da' Python: Essential Reference 'de Beazley para uma rápida Descrição dos geradores", mas muitos outros já postaram boas descrições.
Além disso, note que yield
pode ser usado em coroutinas como o dual da sua utilização em funções geradoras. Embora não seja o mesmo uso que o seu excerto de código, (yield)
pode ser usado como uma expressão numa função. Quando um chamador envia um valor para o método usando o método send()
, então o coroutine irá executar até que seja encontrada a próxima declaração (yield)
.
Os geradores e as coroutinas são uma forma fixe de configurar aplicações do tipo fluxo de dados. Pensei que valeria a pena saber sobre o outro uso da declaração yield
em funções.
Aqui estão alguns exemplos em Python de como realmente implementar geradores como se Python não fornecesse açúcar sintático para eles:
Como gerador de Python:
from itertools import islice
def fib_gen():
a, b = 1, 1
while True:
yield a
a, b = b, a + b
assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))
Utilização de dispositivos lexicais em vez de geradores
def ftake(fnext, last):
return [fnext() for _ in xrange(last)]
def fib_gen2():
#funky scope due to python2.x workaround
#for python 3.x use nonlocal
def _():
_.a, _.b = _.b, _.a + _.b
return _.a
_.a, _.b = 0, 1
return _
assert [1,1,2,3,5] == ftake(fib_gen2(), 5)
Usar o encerramento de objectos em vez de geradores (porque Os fechos e os objectos são equivalentes)
class fib_gen3:
def __init__(self):
self.a, self.b = 1, 1
def __call__(self):
r = self.a
self.a, self.b = self.b, self.a + self.b
return r
assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
Do ponto de vista da programação, os iteradores são implementados como blocos.
Para implementar iteradores, geradores e grupos de linhas para execução simultânea, etc. como troncos (também chamados funções anônimas), uma pessoa usa mensagens enviadas para um objeto de fechamento, que tem um despachante, e o despachante responde para "mensagens".Http://en.wikipedia.org/wiki/Message_passing
"a seguir" é uma mensagem enviada para um encerramento, criada pelo "iter " call.
Existem muitas maneiras de implementar este cálculo. Eu usei mutação, mas é fácil fazê-lo sem mutação, devolvendo o valor atual e o próximo rendimento.
Aqui está uma demonstração que usa a estrutura dos R6RS, mas a semântica é absolutamente idêntica à do Python. é o mesmo modelo de computação, e só é necessária uma mudança de sintaxe para reescrevê-la em Python.
Welcome to Racket v6.5.0.3. -> (define gen (lambda (l) (define yield (lambda () (if (null? l) 'END (let ((v (car l))) (set! l (cdr l)) v)))) (lambda(m) (case m ('yield (yield)) ('init (lambda (data) (set! l data) 'OK)))))) -> (define stream (gen '(1 2 3))) -> (stream 'yield) 1 -> (stream 'yield) 2 -> (stream 'yield) 3 -> (stream 'yield) 'END -> ((stream 'init) '(a b)) 'OK -> (stream 'yield) 'a -> (stream 'yield) 'b -> (stream 'yield) 'END -> (stream 'yield) 'END ->
Como analogia, return
e yield
são gémeos. return
significa "retorno e paragem" enquanto que "Rendimento" significa "Retorno, MAS continuar"
- tenta obter uma lista numérica com
return
.
def num_list(n):
for i in range(n):
return i
Passa.
In [5]: num_list(3)
Out[5]: 0
Veja, você recebe apenas um número em vez de uma lista deles. Nunca permite que prevaleças feliz, apenas ... implementa uma vez e desiste.
- Aí vem
yield
Substituir return
por yield
:
In [10]: def num_list(n):
...: for i in range(n):
...: yield i
...:
In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>
In [12]: list(num_list(3))
Out[12]: [0, 1, 2]
Agora, ganhas para obter todos os números.
Comparando com return
que corre uma vez e para, yield
executa vezes que planeaste.
Você pode interpretar return
como return one of them
, e yield
como return all of them
. Isto chama-se iterable
.
- mais um passo podemos reescrever
yield
a declaração comreturn
In [15]: def num_list(n):
...: result = []
...: for i in range(n):
...: result.append(i)
...: return result
In [16]: num_list(3)
Out[16]: [0, 1, 2]
É o cerne da questão. yield
.
A diferença entre uma lista return
saídas e o objecto yield
saída é:
Você irá sempre obter [0, 1, 2] de um objecto da lista, mas só os poderá recuperar de 'a saída do objecto yield
' uma vez. Então, ele tem um novo nome generator
objeto como mostrado em Out[11]: <generator object num_list at 0x10327c990>
.
-
return
e {[6] } são gémeos -
list
e {[26] } são gémeos
Aqui está um exemplo simples:
def isPrimeNumber(n):
print "isPrimeNumber({}) call".format(n)
if n==1:
return False
for x in range(2,n):
if n % x == 0:
return False
return True
def primes (n=1):
while(True):
print "loop step ---------------- {}".format(n)
if isPrimeNumber(n): yield n
n += 1
for n in primes():
if n> 10:break
print "wiriting result {}".format(n)
Resultado:
loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call
Eu não sou um desenvolvedor Python, mas parece-me yield
mantém a posição do fluxo do programa e o próximo loop começa a partir da posição "yield". Parece que está à espera nessa posição, e pouco antes disso, devolvendo um valor lá fora, e da próxima vez continua a funcionar.
Gosto de pensar num tópico como tendo uma pilha (mesmo quando não é implementada dessa forma).
Quando uma função normal é chamada, ela coloca as suas variáveis locais na pilha, faz alguma computação, então limpa a pilha e retorna. Os valores de suas variáveis locais nunca mais são vistos.
Com uma função yield
, quando o seu código começa a correr (isto é, após a função ser chamada, devolvendo um objecto gerador, cujo next()
o método é então invocado), ele também coloca suas variáveis locais na pilha e calcula por um tempo. Mas então, quando ele atinge a declaração yield
, antes de limpar a sua parte da pilha e retornar, ele tira uma foto de suas variáveis locais e as armazena no objeto gerador. Ele também escreve o lugar onde ele está atualmente até em seu código (ou seja, a declaração particular yield
).
Quando next()
é chamado posteriormente, ele recupera os pertences da função na pilha e re-anima-o. A função continua a calcular a partir de onde parou, alheio ao fato de que tinha acabado de passar uma eternidade em armazenamento frio.
Compare os seguintes exemplos:
def normalFunction():
return
if False:
pass
def yielderFunction():
return
if False:
yield 12
Quando chamamos a segunda função, ela se comporta de forma muito diferente da primeira. A declaração yield
pode ser inalcançável, mas se estiver presente em qualquer lugar, muda a natureza da com o que estamos a lidar.
>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>
Chamar não funciona o código, mas faz um gerador com o código. (Talvez seja uma boa idéia nomear essas coisas com o prefixo yielder
para legibilidade.)
>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
...
'__iter__', #Returns gen itself, to make it work uniformly with containers
... #when given to a for loop. (Containers return an iterator instead.)
'close',
'gi_code',
'gi_frame',
'gi_running',
'next', #The method that runs the function's body.
'send',
'throw']
Os campos gi_code
e gi_frame
são onde o estado congelado está armazenado. Explorando-os com dir(..)
, podemos confirmar que o nosso modelo mental acima é credível.
yield
é usado para criar um gerador de sequência. É usado para gerar uma sequência dinâmica. Por exemplo, ao ler um arquivo linha a linha em uma rede, você pode usar a função yield
da seguinte forma:
def getNextLines():
while con.isOpen():
yield con.read()
Pode usá-lo no seu código da seguinte forma:
for line in getNextLines():
doSomeThing(line)
controlo de execução transferência gotcha
O controlo de execução será transferido de getNextLines() para o laço for
quando o rendimento for executado. Assim, cada vez que getNextLines() é invocado, a execução começa a partir do ponto onde foi parado da última vez.
Assim, em resumo, uma função com o seguinte código
def simpleYield():
yield "first time"
yield "second time"
yield "third time"
yield "Now some useful value {}".format(12)
for i in simpleYield():
print i
Irá imprimir
"first time"
"second time"
"third time"
"Now some useful value 12"
Yield é um objecto
Um return
numa função irá devolver um único valor.
Se quiser uma função para devolver um enorme conjunto de valores , use yield
.
yield
é uma barreira .
Como barreira na língua CUDA, não transferirá o controlo até chegar elaborar.Isto é, irá executar o código na sua função desde o início até atingir
yield
. Então, devolverá o primeiro valor. do loop.
Então, cada outra chamada irá executar o loop que você escreveu na função mais uma vez, retornando o próximo valor até que não haja nenhum valor para retornar.
yield
transforma a sua função numa fábrica que produz um objecto especial chamado a {6]} que envolve o corpo da sua função original. Quando o generator
é iterado, executa a sua função até atingir o próximo yield
, suspende então a execução e avalia o valor passado para yield
. Ele repete este processo em cada iteração até que o caminho de execução saia da função. Por exemplo,
def simple_generator():
yield 'one'
yield 'two'
yield 'three'
for i in simple_generator():
print i
Simplesmente Saídas
one
two
three
A energia vem de usar o gerador com um loop que calcula uma sequência, o gerador executa o loop parando cada vez para 'yield' o resultado seguinte do cálculo, desta forma calcula uma lista na altura, sendo o benefício a memória guardada para cálculos especialmente grandes
Diz que querias criar uma função própria range
que produzisse uma gama iterável de números, podias fazê-lo assim,
def myRangeNaive(i):
n = 0
range = []
while n < i:
range.append(n)
n = n + 1
return range
E usa-o como isto.
for i in myRangeNaive(10):
print i
Mas isto é ineficiente porque
- você cria uma matriz que só usa uma vez (isto desperdiça memória) Este código passa duas vezes por aquela matriz! :(
def myRangeSmart(i):
n = 0
while n < i:
yield n
n = n + 1
return
for i in myRangeSmart(10):
print i
Agora, em cada iteração, uma função no gerador chamada next()
executa a função até atingir uma declaração de 'rendimento' em que pára e "produz" o valor ou atinge o fim da função. Neste caso, na primeira chamada, next()
executa até à instrução yield e yield 'n', na próxima chamada irá executar a instrução incremental, saltar de volta para o 'while', avaliá-lo, e se for verdadeiro, irá parar e produzir' n ' novamente, irá continuar dessa forma até que a condição while retorne falso e o gerador salta para o fim da função.
yield
é como um elemento de retorno para uma função. A diferença é que o elemento yield
transforma uma função num gerador. Um gerador comporta-se como uma função até que algo seja "rendido". O gerador pára até ser chamado a seguir, e continua exatamente do mesmo ponto em que começou. Você pode obter uma sequência de todos os valores' rendidos ' em um, chamando list(generator())
.
(a minha resposta abaixo só fala da perspectiva de usar o gerador Python, não a implementação subjacente do mecanismo gerador , que envolve alguns truques de manipulação de pilha e pilha.)
Quando yield
é usado em vez de um return
numa função python, essa função é transformada em algo especial chamado generator function
. Essa função irá retornar um objeto de generator
Tipo. a palavra-chave yield
é uma bandeira para notificar o compilador python para tratar tal função especialmente. as funções normais terminarão assim que algum valor for devolvido. Mas com a ajuda do compilador, a função do gerador pode ser considerada como refutável. Ou seja, o contexto de execução será restaurado e a execução continuará a partir da última execução. Até que você explicitamente chame return, o que irá levantar uma exceção StopIteration
(que também faz parte do protocolo iterator), ou chegar ao fim da função. Encontrei muitas referências sobre generator
mas este um do functional programming perspective
é o mais digerível.
(Agora eu quero falar sobre a lógica por trás generator
, e o iterator
baseado no meu próprio entendimento. Espero que isto o ajude a compreender a motivação essencial de iterador e gerador. Tal conceito aparece em outras línguas, bem como C#.)
data
diretamente, por que não armazenar algum tipo de metadata
indiretamente, isto é,the logic how the data is computed
.
Existem 2 abordagens para embrulhar esses metadados.
-
A abordagem OO, nós embrulhamos os metadados. Este é o chamado
- a abordagem funcional, nós embrulhamos os metadados
as a function
. Isto é ... os chamadosgenerator function
. Mas sob o capô, o iterador devolvidogenerator object
Porque também implementa o protocolo iterator.
iterator
que implementa o protocolo iterador (isto é, os métodos __next__()
, e __iter__()
). Isto é ... também o padrão de design do iterador geralmente visto.
Muitas pessoas usam return
em vez de yield
, mas, em alguns casos, yield
pode ser mais eficiente e mais fácil de trabalhar.
Aqui está um exemplo que yield
é definitivamente melhor para:
Return (em função)
import random
def return_dates():
dates = [] # With 'return' you need to create a list then return it
for i in range(5):
date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
dates.append(date)
return dates
Yield (em função)
def yield_dates():
for i in range(5):
date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
yield date # 'yield' makes a generator automatically which works
# in a similar way. This is much more efficient.
Funções de chamada
dates_list = return_dates()
print(dates_list)
for i in dates_list:
print(i)
dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
print(i)
Ambas as funções fazem a mesma coisa, mas yield
usa três linhas em vez de cinco e tem uma menos variável para se preocupar.
Como podem ver, ambas as funções fazem a mesma coisa. A única diferença é queEste é o resultado do Código:
return_dates()
dá uma lista e {[9] } dá um gerador.
Um exemplo da vida real seria algo como ler um arquivo linha por linha ou se você só quer fazer um gerador.
A palavra-chave yield
recolhe simplesmente os resultados. Pensa em:return +=
yield
, para calcular a série de fibonacci, explicada:
def fib(limit=50):
a, b = 0, 1
for i in range(limit):
yield b
a, b = b, a+b
Quando introduzires isto no teu REPL e depois tentares chamá-lo, terás um resultado mistificante:
>>> fib()
<generator object fib at 0x7fa38394e3b8>
Isto é porque a presença de yield
sinalizada para Python que você quer criar um gerador , isto é, um objeto que gera valores sob demanda.
next
, ou, indirectamente, alimentando-a a uma construção que consome valores.
Usando a função embutida next()
, invoca directamente.next
/__next__
, forçando o gerador a produzir um valor:
>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5
Indiretamente, se você fornecer fib
a um laço for
, um inicializador list
, um inicializador tuple
, ou qualquer outra coisa que espere um objeto que gera / produz valores, você "consumirá" o gerador até que não mais valores possam ser produzidos por ele (e ele devolve):
results = []
for i in fib(30): # consumes fib
results.append(i)
# can also be accomplished with
results = list(fib(30)) # consumes fib
Da mesma forma, com um inicializador tuple
:
>>> tuple(fib(5)) # consumes fib
(1, 1, 2, 3, 5)
Um gerador difere de uma função no sentido de que é preguiçoso. Consegue-o mantendo o seu estado local e permitindo-lhe retomar sempre que precisar.
Quando invocaste pela primeira vez fib
chamando-lhe:
f = fib()
O Python compila a função, encontra a palavra-chave yield
e simplesmente devolve-lhe um objecto gerador. Não ajuda muito, ao que parece.
Quando em seguida, pedir gera o primeiro valor, direta ou indiretamente, executa todas as declarações que encontra, até encontrar um yield
, então devolve o valor que você forneceu a yield
e faz pausas. Para um exemplo que demonstre melhor isso, vamos usar algumas chamadas print
(Substituir por print "text"
se em Python 2):
def yielder(value):
""" This is an infinite generator. Only use next on it """
while 1:
print("I'm going to generate the value for you")
print("Then I'll pause for a while")
yield value
print("Let's go through it again.")
Agora, entra no REPL:
>>> gen = yielder("Hello, yield!")
Tem um objecto gerador à espera de um comando para gerar um valor. Use next
e veja o que é impresso:
>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'
Os resultados não citados são o que está impresso. O resultado Citado é o que é devolvido de yield
. Ligue para {[12] } novamente agora:
>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'
O gerador lembra-se que estava em pausa e recomeça a partir daí. A próxima mensagem é impressa e a busca pela declaração yield
para pausar nela realizada novamente (devido ao laço while
).
Outro TL; DR
Iterador na lista: next()
devolve o próximo elemento da lista
Gerador de iteradores: next()
irá calcular o próximo elemento na altura (executar o código)
Você pode ver o rendimento / gerador como uma forma de executar manualmente o fluxo de controlo de fora( como continuar o ciclo um passo), chamando next
, por mais complexo que seja o fluxo.
Nota : o gerador é Não {[9] } uma função normal. Lembra-se do estado anterior como variáveis locais (pilha). Para mais informações, ver outras respostas ou artigos. O gerador só pode ser iterado uma vez. Você poderia fazer sem yield
, mas não seria tão agradável, assim que pode ser considerado' muito agradável ' língua açúcar.