Para entender o que yield
faz, você deve entender o que são geradores . E antes que você possa entender geradores, você deve entender iterables .
Iterables
Ao criar uma lista, você pode ler seus itens um por um. A leitura de seus itens um por um é chamada iteração:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
é um iterável . Ao usar uma compreensão de lista, você cria uma lista e, portanto, é iterável:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
Tudo o que você pode usar " for... in...
" é iterável; lists
, strings
arquivos ...
Essas iteráveis são úteis porque você pode lê-las quantas vezes quiser, mas armazena todos os valores na memória e isso nem sempre é o que você deseja quando possui muitos valores.
Geradores
Geradores são iteradores, um tipo de iterável que você pode repetir apenas uma vez . Os geradores não armazenam todos os valores na memória, eles geram os valores rapidamente :
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
É o mesmo, exceto que você usou em ()
vez de []
. MAS, você não pode executar for i in mygenerator
uma segunda vez, uma vez que os geradores podem ser usados apenas uma vez: eles calculam 0, esquecem-no e calculam 1 e terminam o cálculo 4, um por um.
Produção
yield
é uma palavra-chave usada como return
, exceto que a função 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 você sabe que sua função retornará um enorme conjunto de valores que você precisará ler apenas uma vez.
Para dominar yield
, você deve entender que, quando você chama a função, o código que você escreveu no corpo da função não é executado. A função retorna apenas o objeto gerador, isso é um pouco complicado :-)
Em seguida, seu código continuará de onde parou sempre que for
usar o gerador.
Agora a parte mais difícil:
A primeira vez que for
chamar o objeto gerador criado a partir da sua função, ele executará o código na sua função desde o início até que acerte yield
, e retornará o primeiro valor do loop. Em seguida, cada chamada subsequente executará outra iteração do loop que você escreveu na função e retornará o próximo valor. Isso continuará até que o gerador seja considerado vazio, o que acontece quando a função é executada sem bater yield
. Isso pode ser porque o loop chegou ao fim ou porque você não satisfaz mais um "if/else"
.
Seu 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 the 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 the 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
Chamador:
# 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 candidate's 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 passar por todos esses dados aninhados, mesmo que seja um pouco perigoso, pois você pode acabar com um loop infinito. Nesse caso, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
esgote todos os valores do gerador, mas while
continue criando novos objetos geradores que produzirão valores diferentes dos anteriores, pois não são aplicados no mesmo nó.
O extend()
método é um método de objeto de lista que espera uma iterável e adiciona seus valores à lista.
Geralmente passamos uma lista para ele:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
Mas no seu código, ele obtém um gerador, o que é bom porque:
- Você não precisa ler os valores duas vezes.
- Você pode ter muitos filhos e não os quer armazenados na memória.
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 ele funcione com strings, listas, tuplas e geradores! Isso é chamado de digitação de pato e é uma das razões pelas quais o Python é tão legal. Mas isso é outra história, para outra pergunta ...
Você pode parar por aqui ou ler um pouco para ver um uso avançado de um gerador:
Controlando a exaustão do gerador
>>> 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 Python 3, use print(corner_street_atm.__next__())
ouprint(next(corner_street_atm))
Pode ser útil para várias coisas, como controlar o acesso a um recurso.
Itertools, seu melhor amigo
O módulo itertools contém funções especiais para manipular iteráveis. Já desejou duplicar um gerador? Cadeia de dois geradores? Agrupar valores em uma lista aninhada com uma linha única? Map / Zip
sem criar outra lista?
Então apenas import itertools
.
Um exemplo? Vamos ver as possíveis ordens de chegada para uma corrida de quatro cavalos:
>>> 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)]
Entendendo os mecanismos internos da iteração
A iteração é um processo que implica iteráveis (implementando o __iter__()
método) e iteradores (implementando o __next__()
método). Iteráveis são quaisquer objetos dos quais você pode obter um iterador. Iteradores são objetos que permitem iterar em iterables.
Há mais sobre isso neste artigo sobre como os for
loops funcionam .
yield
não é tão mágico que esta resposta sugere. Quando você chama uma função que contém umayield
instrução em qualquer lugar, obtém um objeto gerador, mas nenhum código é executado. Então, toda vez que você extrai um objeto do gerador, o Python executa o código na função até chegar a umayield
instrução, pausa e entrega o objeto. Quando você extrai outro objeto, o Python continua logo após oyield
e continua até chegar a outroyield
(geralmente o mesmo, mas uma iteração posteriormente). Isso continua até que a função termine no final, momento em que o gerador é considerado esgotado.()
vez de[]
, especificamente o que()
é (pode haver confusão com uma tupla).return
declaração. (return
é permitido em uma função que contenhayield
, desde que não especifique um valor de retorno.)Atalho para o entendimento
yield
Quando você
yield
vir uma função com instruções, aplique este truque fácil para entender o que acontecerá:result = []
no início da função.yield expr
porresult.append(expr)
.return result
na parte inferior da função.yield
declarações! Leia e descubra o código.Esse truque pode fornecer uma idéia da lógica por trás da função, mas o que realmente acontece
yield
é significativamente diferente do que acontece na abordagem baseada em lista. Em muitos casos, a abordagem de rendimento também será muito mais eficiente em termos de memória e mais rápida. Em outros casos, esse truque o deixará preso em um loop infinito, mesmo que a função original funcione perfeitamente. Continue lendo para saber mais ...Não confunda seus iteráveis, iteradores e geradores
Primeiro, o protocolo iterador - quando você escreve
O Python executa as duas etapas a seguir:
Obtém um iterador para
mylist
:Call
iter(mylist)
-> retorna um objeto com umnext()
método (ou__next__()
no Python 3).[Este é o passo que a maioria das pessoas esquece de contar]
Usa o iterador para fazer um loop sobre itens:
Continue chamando o
next()
método no iterador retornado da etapa 1. O valor de retorno denext()
é atribuído ax
e o corpo do loop é executado. Se uma exceçãoStopIteration
é gerada de dentronext()
, significa que não há mais valores no iterador e o loop é encerrado.A verdade é que o Python executa as duas etapas acima sempre que quiser fazer um loop sobre o conteúdo de um objeto - então pode ser um loop for, mas também pode ser semelhante ao código
otherlist.extend(mylist)
(ondeotherlist
está uma lista do Python).Aqui
mylist
está um iterável porque implementa o protocolo iterador. Em uma classe definida pelo usuário, você pode implementar o__iter__()
método para tornar as instâncias da sua classe iteráveis. Este método deve retornar um iterador . Um iterador é um objeto com umnext()
método É possível implementar ambos__iter__()
enext()
na mesma classe e ter__iter__()
retornoself
. Isso funcionará em casos simples, mas não quando você desejar dois iteradores fazendo loop no mesmo objeto ao mesmo tempo.Portanto, esse é o protocolo do iterador, muitos objetos implementam esse protocolo:
__iter__()
.Observe que um
for
loop não sabe com que tipo de objeto está lidando - apenas segue o protocolo do iterador e fica feliz em obter item após item, conforme chamanext()
. As listas internas retornam seus itens um por um, os dicionários retornam as chaves uma a uma, os arquivos retornam as linhas uma a uma etc. E os geradores retornam ... bem, é aí queyield
entra:Em vez de
yield
instruções, se você tivesse trêsreturn
instruções,f123()
apenas a primeira seria executada e a função sairia. Masf123()
não é uma função comum. Quandof123()
é chamado, ele não retorna nenhum dos valores nas declarações de rendimento! Retorna um objeto gerador. Além disso, a função não sai realmente - entra em um estado suspenso. Quando ofor
loop tenta fazer um loop sobre o objeto gerador, a função retoma de seu estado suspenso na linha seguinte após ayield
volta anterior, executa a próxima linha de código, nesse caso, umayield
instrução e retorna como a próxima item. Isso acontece até a função sair, quando o gerador aumentaStopIteration
e o loop sai.Portanto, o objeto gerador é como um adaptador - em uma extremidade, ele exibe o protocolo iterador, expondo
__iter__()
enext()
métodos para manter ofor
loop feliz. No outro extremo, no entanto, ele executa a função apenas o suficiente para obter o próximo valor e o coloca novamente no modo suspenso.Por que usar geradores?
Geralmente, você pode escrever um código que não use geradores, mas implemente a mesma lógica. Uma opção é usar o 'truque' temporário da lista que mencionei antes. Isso não funcionará em todos os casos, por exemplo, se você tiver loops infinitos, ou pode fazer uso ineficiente da memória quando você tiver uma lista realmente longa. A outra abordagem é implementar uma nova classe iterável SomethingIter que mantém o estado nos membros da instância e executa a próxima etapa lógica no método
next()
(ou__next__()
no Python 3). Dependendo da lógica, o código dentro donext()
método pode acabar parecendo muito complexo e propenso a erros. Aqui, os geradores fornecem uma solução limpa e fácil.fonte
send
entrar em um gerador, que é uma grande parte do objetivo dos geradores?otherlist.extend(mylist)
" -> Isso está incorreto.extend()
modifica a lista no local e não retorna um iterável. Tentar fazer um loop overotherlist.extend(mylist)
falhará com umTypeError
porqueextend()
implicitamente retornaNone
, e você não poderá fazer o loop overNone
.mylist
(não ativadasotherlist
) durante a execuçãootherlist.extend(mylist)
.Pense desta maneira:
Um iterador é apenas um termo sofisticado para um objeto que possui um
next()
método. Portanto, uma função de produção acaba sendo algo como isto:Versão original:
Isso é basicamente o que o intérprete Python faz com o código acima:
Para obter mais informações sobre o que está acontecendo nos bastidores, o
for
loop pode ser reescrito para isso:Isso faz mais sentido ou apenas te confunde mais? :)
Devo observar que isso é uma simplificação excessiva para fins ilustrativos. :)
fonte
__getitem__
pode ser definido em vez de__iter__
. Por exemplo:,class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i)
Ele imprimirá: 0, 10, 20, ..., 90iterator = some_function()
, a variáveliterator
não terá mais uma função chamadanext()
, mas apenas uma__next__()
função. Pensei em mencionar.for
implementação do loop que você escreveu chama o__iter__
métodoiterator
, a instância instanciadait
?A
yield
palavra-chave é reduzida a dois fatos simples:yield
palavra - chave em qualquer lugar dentro de uma função, essa função não retornará mais por meio dareturn
instrução Em vez disso , ele retorna imediatamente um objeto "lista pendente" lento chamado geradorlist
ouset
ourange
ou dict-view, com um built-in de protocolo para visitar cada elemento em uma determinada ordem .Em poucas palavras: um gerador é uma lista lenta e incrementalmente pendente , e as
yield
instruções permitem que você use a notação de função para programar os valores da lista que o gerador deve citar gradualmente.Exemplo
Vamos definir uma função
makeRange
semelhante à do Pythonrange
. ChamarmakeRange(n)
RETORNA UM GERADOR:Para forçar o gerador a retornar imediatamente seus valores pendentes, você pode passá-lo para
list()
(da mesma forma que você poderia iterável):Comparando o exemplo com "apenas retornando uma lista"
O exemplo acima pode ser pensado como meramente criando uma lista à qual você anexa e retorna:
Há uma grande diferença, no entanto; veja a última seção.
Como você pode usar geradores
Um iterável é a última parte da compreensão de uma lista e todos os geradores são iteráveis; portanto, eles são frequentemente usados assim:
Para ter uma idéia melhor dos geradores, você pode brincar com o
itertools
módulo (certifique-se de usá-lochain.from_iterable
e nãochain
quando necessário). Por exemplo, você pode até usar geradores para implementar listas preguiçosas infinitamente longas comoitertools.count()
. Você pode implementar por conta própriadef enumerate(iterable): zip(count(), iterable)
ou alternativamente com ayield
palavra - chave em um loop while.Observe: os geradores podem realmente ser usados para muitas outras coisas, como implementar corotinas ou programação não determinística ou outras coisas elegantes. No entanto, o ponto de vista das "listas preguiçosas" que apresento aqui é o uso mais comum que você encontrará.
Por trás das cenas
É assim que o "protocolo de iteração Python" funciona. Ou seja, o que está acontecendo quando você faz
list(makeRange(5))
. Isso é o que eu descrevi anteriormente como uma "lista lenta e incremental".A função
next()
interna apenas chama a.next()
função de objetos , que faz parte do "protocolo de iteração" e é encontrada em todos os iteradores. Você pode usar manualmente anext()
função (e outras partes do protocolo de iteração) para implementar coisas sofisticadas, geralmente à custa da legibilidade, portanto, evite fazer isso ...Minutiae
Normalmente, a maioria das pessoas não se importaria com as seguintes distinções e provavelmente desejaria parar de ler aqui.
No Python-speak, iterável é qualquer objeto que "entende o conceito de loop for" como uma lista
[1,2,3]
e um iterador é uma instância específica do like for loop solicitado[1,2,3].__iter__()
. Um gerador é exatamente o mesmo que qualquer iterador, exceto pela forma como foi gravado (com sintaxe da função).Quando você solicita um iterador de uma lista, ele cria um novo iterador. No entanto, quando você solicita um iterador de um iterador (o que você raramente faria), ele fornece uma cópia de si mesmo.
Assim, no caso improvável de você estar deixando de fazer algo assim ...
... lembre-se de que um gerador é um iterador ; isto é, é de uso único. Se você deseja reutilizá-lo, ligue
myRange(...)
novamente. Se você precisar usar o resultado duas vezes, converta o resultado em uma lista e armazene-o em uma variávelx = list(myRange(5))
. Aqueles que absolutamente precisam clonar um gerador (por exemplo, que estão fazendo uma metaprogramação assustadoramente hackiana) podem usar,itertools.tee
se absolutamente necessário, uma vez que a proposta de padrões do iterador copiável do Python PEP foi adiada.fonte
Resumo da resposta / resumo
yield
, quando chamada, retorna um gerador .yield from
.return
um gerador.)Geradores:
yield
é legal apenas dentro de uma definição de função, e a inclusão deyield
em uma definição de função faz com que ele retorne um gerador.A idéia para geradores vem de outros idiomas (consulte a nota de rodapé 1) com diversas implementações. Nos Geradores do Python, a execução do código é congelada no ponto do rendimento. Quando o gerador é chamado (os métodos são discutidos abaixo), a execução é retomada e congela no próximo rendimento.
yield
fornece uma maneira fácil de implementar o protocolo do iterador , definido pelos dois métodos a seguir:__iter__
enext
(Python 2) ou__next__
(Python 3). Ambos os métodos transformam um objeto em um iterador que você pode verificar com aIterator
Classe Base Abstrata docollections
módulo.O tipo de gerador é um subtipo de iterador:
E, se necessário, podemos verificar o seguinte:
Uma característica de um
Iterator
é que, uma vez esgotado , você não pode reutilizá-lo ou redefini-lo:Você precisará fazer outro se quiser usar sua funcionalidade novamente (consulte a nota de rodapé 2):
Pode-se produzir dados programaticamente, por exemplo:
O gerador simples acima também é equivalente ao abaixo - a partir do Python 3.3 (e não disponível no Python 2), você pode usar
yield from
:No entanto,
yield from
também permite delegação a subgeradores, o que será explicado na seção a seguir sobre delegação cooperativa com subcorotinas.Corotinas:
yield
forma uma expressão que permite que os dados sejam enviados ao gerador (consulte a nota de rodapé 3)Aqui está um exemplo, tome nota da
received
variável, que apontará para os dados que são enviados ao gerador:Primeiro, devemos colocar o gerador em fila com a função incorporada
next
,. Ele vai chamar o apropriadonext
ou__next__
método, dependendo da versão do Python que você está usando:E agora podemos enviar dados para o gerador. ( Enviar
None
é o mesmo que ligarnext
.):Delegação Cooperativa à Sub-Corotina com
yield from
Agora, lembre-se de que
yield from
está disponível no Python 3. Isso nos permite delegar corotinas a uma subcorotina:E agora podemos delegar funcionalidade a um subgerador e ele pode ser usado por um gerador, como acima:
Você pode ler mais sobre a semântica precisa de
yield from
no PEP 380.Outros métodos: fechar e atirar
O
close
método aumentaGeneratorExit
no ponto em que a execução da função foi congelada. Isso também será chamado__del__
para que você possa colocar qualquer código de limpeza onde manipularGeneratorExit
:Você também pode lançar uma exceção que pode ser manipulada no gerador ou propagada de volta para o usuário:
Conclusão
Acredito ter coberto todos os aspectos da seguinte pergunta:
Acontece que isso
yield
faz muito. Tenho certeza de que poderia adicionar exemplos ainda mais detalhados a isso. Se você quiser mais ou tiver alguma crítica construtiva, deixe-me saber comentando abaixo.Apêndice:
Crítica da resposta principal / aceita **
__iter__
método retornando um iterador . Um iterador fornece um método.next
(Python 2 ou.__next__
(Python 3), que é implicitamente chamado porfor
loops até que seja geradoStopIteration
e, assim que for, continuará a fazê-lo.yield
ponto..next
método, quando, em vez disso, ele deve usar a função embutidanext
,. Seria uma camada de indireção apropriada, porque seu código não funciona no Python 3.yield
faz.yield
acompanham a nova funcionalidadeyield from
do Python 3. A resposta superior / aceita é uma resposta muito incompleta.Crítica da resposta, sugerindo
yield
uma expressão ou compreensão geradora.Atualmente, a gramática permite qualquer expressão na compreensão de uma lista.
Como rendimento é uma expressão, alguns consideram interessante usá-lo em compreensões ou expressões geradoras - apesar de não citar nenhum caso de uso particularmente bom.
Os desenvolvedores principais do CPython estão discutindo a depreciação de sua permissão . Aqui está uma postagem relevante da lista de discussão:
Além disso, há um problema pendente (10544) que parece apontar na direção de que isso nunca seja uma boa ideia (PyPy, uma implementação do Python escrita em Python, já está levantando avisos de sintaxe.)
Resumindo, até que os desenvolvedores do CPython nos digam o contrário: não coloque
yield
uma expressão ou compreensão geradora.A
return
declaração em um geradorNo Python 2 :
An
expression_list
é basicamente qualquer número de expressões separadas por vírgulas - essencialmente, no Python 2, você pode parar o gerador comreturn
, mas não pode retornar um valor.No Python 3 :
Notas de rodapé
As linguagens CLU, Sather e Icon foram referenciadas na proposta para introduzir o conceito de geradores no Python. A ideia geral é que uma função possa manter o estado interno e gerar pontos de dados intermediários sob demanda do usuário. Isso prometeu ter desempenho superior a outras abordagens, incluindo o Python Threading , que nem está disponível em alguns sistemas.
Isso significa, por exemplo, que os
xrange
objetos (range
no Python 3) não sãoIterator
s, mesmo que sejam iteráveis, porque podem ser reutilizados. Como listas, seus__iter__
métodos retornam objetos iteradores.yield
foi originalmente introduzido como uma declaração, o que significa que só poderia aparecer no início de uma linha em um bloco de código. Agorayield
cria uma expressão de rendimento. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Esta alteração foi proposta para permitir que um usuário envie dados para o gerador da mesma forma que se poderia receber. Para enviar dados, é preciso poder atribuí-los a alguma coisa e, para isso, uma declaração simplesmente não funcionará.fonte
yield
é exatamente comoreturn
- retorna o que você deseja (como um gerador). A diferença é que na próxima vez em que você ligar para o gerador, a execução começará da última chamada para ayield
instrução. Diferentemente do retorno, o quadro da pilha não é limpo quando ocorre um rendimento, no entanto, o controle é transferido de volta para o chamador, portanto seu estado será retomado na próxima vez que a função for chamada.No caso do seu código, a função
get_child_candidates
está agindo como um iterador, de modo que, quando você estende sua lista, ele adiciona um elemento de cada vez à nova lista.list.extend
chama um iterador até que esteja esgotado. No caso do exemplo de código que você postou, seria muito mais claro retornar uma tupla e anexá-la à lista.fonte
Há uma coisa extra a ser mencionada: uma função que produz realmente não precisa terminar. Eu escrevi código assim:
Então eu posso usá-lo em outro código como este:
Isso realmente ajuda a simplificar alguns problemas e facilita algumas coisas.
fonte
Para aqueles que preferem um exemplo de trabalho mínimo, medite nesta sessão interativa do Python:
fonte
TL; DR
Em vez disso:
faça isso:
Sempre que você se encontrar criando uma lista do zero,
yield
cada peça é substituída.Este foi o meu primeiro momento "aha" com rendimento.
yield
é uma maneira açucarada de dizerMesmo comportamento:
Comportamento diferente:
O rendimento é de passagem única : você pode iterar apenas uma vez. Quando uma função tem um rendimento nela, chamamos de função geradora . E um iterador é o que ele retorna. Esses termos são reveladores. Perdemos a conveniência de um contêiner, mas obtemos o poder de uma série calculada conforme necessário e arbitrariamente longa.
O rendimento é preguiçoso , adia a computação. Uma função com um rendimento na verdade não é executada quando você a chama. Ele retorna um objeto iterador que lembra de onde parou. Cada vez que você chama
next()
o iterador (isso acontece em um loop for), a polegada avança para o próximo rendimento.return
gera StopIteration e encerra a série (esse é o fim natural de um loop for).O rendimento é versátil . Os dados não precisam ser armazenados juntos, eles podem ser disponibilizados um de cada vez. Pode ser infinito.
Se você precisar de vários passes e a série não for muito longa, basta ligar
list()
para ele:Escolha brilhante da palavra
yield
porque ambos os significados se aplicam:... forneça os próximos dados da série.
... renuncie à execução da CPU até o iterador avançar.
fonte
O rendimento fornece um gerador.
Como você pode ver, no primeiro caso,
foo
mantém a lista inteira na 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? Isso não apenas é um grande consumidor de memória, mas também custa muito tempo para ser construído no momento em que a função é chamada.No segundo caso,
bar
apenas fornece um gerador. Um gerador é iterável - o que significa que você pode usá-lo em umfor
loop, etc., mas cada valor pode ser acessado apenas uma vez. Todos os valores também não são armazenados na memória ao mesmo tempo; o objeto gerador "lembra" onde estava o loop na última vez em que você o chamou - dessa forma, se você estiver usando um iterável para (digamos) contar até 50 bilhões, não precisará contar até 50 bilhões de uma vez e armazene os 50 bilhões de números para contar.Novamente, este é um exemplo bem elaborado, você provavelmente usaria ferramentas se realmente quisesse contar até 50 bilhões. :)
Este é o caso de uso mais simples de geradores. Como você disse, ele pode ser usado para escrever permutações eficientes, usando yield para fazer as coisas subirem pela pilha de chamadas em vez de usar algum tipo de variável de pilha. Os geradores também podem ser usados para travessias especializadas de árvores e todo tipo de outras coisas.
fonte
range
também retorna um gerador em vez de uma lista; portanto, você também vê uma idéia semelhante, exceto que__repr__
/__str__
é substituída para mostrar um resultado melhor, nesse casorange(1, 10, 2)
.Está retornando um gerador. Eu não estou particularmente familiarizado com o Python, mas acredito que é o mesmo tipo de bloco iterador do C # se você estiver familiarizado com eles.
A idéia principal é que o compilador / intérprete / o que quer que faça algum truque para que, no que diz respeito ao chamador, ele possa continuar chamando next () e ele continuará retornando valores - como se o método do gerador estivesse em pausa . Agora, obviamente, você não pode realmente "pausar" um método, de modo que o compilador constrói uma máquina de estado para você lembrar onde está atualmente e como são as variáveis locais, etc. Isso é muito mais fácil do que escrever um iterador.
fonte
Há um tipo de resposta que ainda não me foi dado, dentre as muitas ótimas respostas que descrevem como usar geradores. Aqui está a resposta da teoria da linguagem de programação:
A
yield
declaração em Python retorna um gerador. Um gerador em Python é uma função que retorna continuações (e especificamente um tipo de corotina, mas continuações representam o mecanismo mais geral para entender o que está acontecendo).As continuações na teoria das linguagens de programação são um tipo de computação muito mais fundamental, mas não são frequentemente usadas, porque são extremamente difíceis de raciocinar e também muito difíceis de implementar. Mas a idéia do que é uma continuação é direta: é o estado de uma computação que ainda não terminou. Nesse estado, os valores atuais das variáveis, as operações que ainda precisam ser executadas etc. são salvas. Então, em algum momento posterior do programa, a continuação poderá ser chamada, de modo que as variáveis do programa sejam redefinidas para esse estado e as operações salvas sejam executadas.
As continuações, nesta forma mais geral, podem ser implementadas de duas maneiras. No
call/cc
caminho, a pilha do programa é literalmente salva e, quando a continuação é chamada, a pilha é restaurada.No estilo de passagem de continuação (CPS), continuações são apenas funções normais (somente em idiomas onde as funções são de primeira classe) que o programador gerencia e explicitamente gerencia e repassa para as sub-rotinas. Nesse estilo, o estado do programa é representado por fechamentos (e as variáveis que são codificadas neles), em vez de variáveis que residem em algum lugar da pilha. As funções que gerenciam o fluxo de controle aceitam a continuação como argumentos (em algumas variações do CPS, as funções podem aceitar várias continuações) e manipulam o fluxo de controle chamando-as simplesmente chamando-as e retornando posteriormente. Um exemplo muito simples de estilo de passagem de continuação é o seguinte:
Neste exemplo (muito simplista), o programador salva a operação de gravar o arquivo em uma continuação (que pode ser uma operação muito complexa com muitos detalhes a serem gravados) e depois passa essa continuação (por exemplo, fechamento de classe) para outro operador que faz mais processamento e depois o chama, se necessário. (Eu uso muito esse padrão de design na programação da GUI real, porque me salva linhas de código ou, mais importante, para gerenciar o fluxo de controle após o acionamento dos eventos da GUI.)
O restante deste post, sem perda de generalidade, conceituará continuações como CPS, porque é muito mais fácil de entender e ler.
Agora vamos falar sobre geradores em Python. Geradores são um subtipo específico de continuação. Enquanto as continuações geralmente são capazes de salvar o estado de uma computação (ou seja, a pilha de chamadas do programa), os geradores só podem salvar o estado da iteração em um iterador . Embora essa definição seja um pouco enganadora para certos casos de uso de geradores. Por exemplo:
É claramente uma iterável razoável, cujo comportamento é bem definido - sempre que o gerador itera, ele retorna 4 (e faz isso para sempre). Mas provavelmente não é o tipo de iterável prototípico que vem à mente quando se pensa em iteradores (ie
for x in collection: do_something(x)
). Este exemplo ilustra o poder dos geradores: se algo é um iterador, um gerador pode salvar o estado de sua iteração.Para reiterar: As continuações podem salvar o estado da pilha de um programa e os geradores podem salvar o estado da iteração. Isso significa que as continuações são muito mais poderosas que os geradores, mas também que os geradores são muito, muito mais fáceis. Eles são mais fáceis de implementar pelo designer de linguagem e mais fáceis de usar pelo programador (se você tiver algum tempo para gravar, tente ler e entender esta página sobre continuações e chamar / cc ).
Mas você pode facilmente implementar (e conceituar) geradores como um caso simples e específico de estilo de passagem de continuação:
Sempre que
yield
é chamado, ele informa a função para retornar uma continuação. Quando a função é chamada novamente, ela começa de onde parou. Portanto, no pseudo-pseudocódigo (ou seja, não no pseudocódigo, mas não no código), onext
método do gerador é basicamente o seguinte:onde a
yield
palavra-chave é realmente açúcar sintático para a função de gerador real, basicamente algo como:Lembre-se de que este é apenas um pseudocódigo 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
yield
palavra - chave.fonte
Aqui está um exemplo em linguagem simples. Fornecerei uma correspondência entre conceitos humanos de alto nível e conceitos Python de baixo nível.
Quero operar em uma sequência de números, mas não quero me preocupar com a criação dessa sequência, quero apenas me concentrar na operação que quero fazer. Então, eu faço o seguinte:
Este passo corresponde à entrada da
def
função do gerador, ou seja, a função que contém ayield
.Esta etapa corresponde à chamada da função de gerador que retorna um objeto gerador. Observe que você ainda não me disse números; você apenas pega seu papel e lápis.
Esta etapa corresponde à chamada
.next()
no objeto gerador.Esta etapa corresponde ao objeto gerador que encerra seu trabalho e gera uma
StopIteration
exceção. A função gerador não precisa gerar a exceção. É gerado automaticamente quando a função termina ou emite areturn
.É isso que um gerador faz (uma função que contém a
yield
); começa a executar, pausa sempre que faz ayield
e, quando solicitado por um.next()
valor, continua do ponto em que foi o último. Ele se encaixa perfeitamente no design com o protocolo iterador do Python, que descreve como solicitar valores seqüencialmente.O usuário mais famoso do protocolo iterador é o
for
comando no Python. Portanto, sempre que você faz um:não importa se
sequence
é uma lista, uma string, um dicionário ou um objeto gerador como descrito acima; o resultado é o mesmo: você lê os itens de uma sequência, um por um.Observe que
def
inserir uma função que contém umayield
palavra-chave não é a única maneira de criar um gerador; é apenas a maneira mais fácil de criar uma.Para obter informações mais precisas, leia sobre os tipos de iteradores , a declaração de rendimento e os geradores na documentação do Python.
fonte
Embora muitas respostas mostrem por que você usaria a
yield
para criar um gerador, há mais usos parayield
. É muito fácil fazer uma rotina, o que permite a passagem de informações entre dois blocos de código. Não repetirei nenhum dos bons exemplos que já foram dados sobre o usoyield
para criar um gerador.Para ajudar a entender o que a
yield
faz no código a seguir, você pode usar o dedo para rastrear o ciclo através de qualquer código que possua ayield
. Toda vez que seu dedo toca noyield
, você precisa aguardar a inserção de umnext
ou umsend
. Quando anext
é chamado, você rastreia o código até pressionar oyield
... o código à direita doyield
é avaliado e retornado ao chamador ... e então você espera. Quandonext
é chamado novamente, você executa outro loop pelo código. No entanto, você notará que em uma rotinayield
também pode ser usado com umsend
… que enviará um valor do chamador para a função de retorno. Se asend
é dado, entãoyield
recebe o valor enviado e cospe no lado esquerdo ... então o rastreamento através do código progride até que você pressioneyield
novamente (retornando o valor no final, como senext
fosse chamado).Por exemplo:
fonte
Há outro
yield
uso e significado (desde o Python 3.3):Do PEP 380 - Sintaxe para delegar a um subgerador :
Além disso, isso apresentará (desde o Python 3.5):
para evitar que as corotinas sejam confundidas com um gerador comum (hoje
yield
é usado em ambos).fonte
Todas as ótimas respostas, porém um pouco difíceis para iniciantes.
Presumo que você tenha aprendido a
return
afirmação.Como uma analogia,
return
eyield
são gêmeos.return
significa 'retornar e parar', enquanto 'rendimento' significa 'retornar, mas continuar'Executá-lo:
Veja, você obtém apenas um único número em vez de uma lista deles.
return
nunca permite que você prevaleça feliz, apenas implementa uma vez e sai.Substitua
return
poryield
:Agora, você ganha para obter todos os números.
Comparando com o
return
que é executado uma vez e para, osyield
tempos de execução planejados. Você pode interpretarreturn
comoreturn one of them
, eyield
comoreturn all of them
. Isso é chamadoiterable
.É o núcleo sobre
yield
.A diferença entre as
return
saídas de uma lista e ayield
saída do objeto é:Você sempre obterá [0, 1, 2] de um objeto de lista, mas somente poderá recuperá-los da '
yield
saída do objeto ' uma vez. Portanto, ele possui um novogenerator
objeto de nome, conforme exibido emOut[11]: <generator object num_list at 0x10327c990>
.Em conclusão, como uma metáfora para grok-lo:
return
eyield
são gêmeoslist
egenerator
são gêmeosfonte
yield
. Acho que isso é importante e deve ser expresso.Aqui estão alguns exemplos de Python de como realmente implementar geradores como se o Python não fornecesse açúcar sintático para eles:
Como um gerador Python:
Usando fechamentos lexicais em vez de geradores
Usando fechamentos de objetos em vez de geradores (porque ClosuresAndObjectsAreEquivalent )
fonte
Eu publicaria "leia a página 19 do 'Python: Essential Reference' de Beazley para uma descrição rápida dos geradores", mas muitos outros já publicaram boas descrições.
Além disso, observe que
yield
pode ser usado em corotinas como o duplo uso em funções de gerador. Embora não seja o mesmo uso que seu snippet de código,(yield)
pode ser usado como uma expressão em uma função. Quando um chamador envia um valor para o método usando osend()
método, a corotina será executada até a próxima(yield)
instrução seja encontrada.Geradores e corotinas são uma maneira legal de configurar aplicativos do tipo fluxo de dados. Eu pensei que valeria a pena conhecer o outro uso da
yield
declaração em funções.fonte
Do ponto de vista da programação, os iteradores são implementados como thunks .
Para implementar iteradores, geradores e conjuntos de encadeamentos para execução simultânea etc. como thunks (também chamados de funções anônimas), usa-se mensagens enviadas para um objeto de fechamento, que possui um despachante, e o despachante responde a "mensagens".
http://en.wikipedia.org/wiki/Message_passing
" next " é uma mensagem enviada para um encerramento, criada pelo " iter chamada " ".
Existem várias maneiras de implementar esse cálculo. Eu usei mutação, mas é fácil fazê-lo sem mutação, retornando o valor atual e o próximo usuário.
Aqui está uma demonstração que usa a estrutura do R6RS, mas a semântica é absolutamente idêntica à do Python. É o mesmo modelo de computação, e apenas uma mudança na sintaxe é necessária para reescrevê-la no Python.
fonte
Aqui está um exemplo simples:
Resultado:
Não sou desenvolvedor de Python, mas parece-me
yield
mantém a posição do fluxo do programa e o próximo loop começa na posição "yield". Parece que está esperando nessa posição, e pouco antes disso, retornando um valor para fora, e da próxima vez continua a funcionar.Parece ser uma habilidade interessante e interessante: D
fonte
Aqui está uma imagem mental do que
yield
faz.Eu gosto de pensar em um thread como tendo uma pilha (mesmo quando não é implementada dessa maneira).
Quando uma função normal é chamada, ela coloca suas variáveis locais na pilha, faz alguns cálculos, depois limpa a pilha e retorna. Os valores de suas variáveis locais nunca são vistos novamente.
Com uma
yield
função, quando seu código começa a ser executado (ou seja, depois que a função é chamada, retornando um objeto gerador, cujonext()
método é então invocado), ele também coloca suas variáveis locais na pilha e calcula por um tempo. Porém, quando atinge ayield
instrução, antes de limpar sua parte da pilha e retornar, tira um instantâneo de suas variáveis locais e as armazena no objeto gerador. Ele também anota o local em que está atualmente em seu código (ou seja, ayield
instrução específica ).Portanto, é uma espécie de função congelada na qual o gerador está pendurado.
Quando
next()
é chamado posteriormente, ele recupera os pertences da função na pilha e a anima novamente. A função continua a computar de onde parou, alheia ao fato de ter passado apenas uma eternidade em armazenamento refrigerado.Compare os seguintes exemplos:
Quando chamamos a segunda função, ela se comporta de maneira muito diferente da primeira. A
yield
declaração pode estar inacessível, mas se estiver presente em qualquer lugar, muda a natureza do que estamos lidando.A chamada
yielderFunction()
não executa seu código, mas gera um gerador a partir do código. (Talvez seja uma boa idéia nomear essas coisas com oyielder
prefixo de legibilidade.)Os campos
gi_code
egi_frame
são onde o estado congelado é armazenado. Explorando-osdir(..)
, podemos confirmar que nosso modelo mental acima é credível.fonte
Como toda resposta sugere,
yield
é usado para criar um gerador de sequência. É usado para gerar alguma sequência dinamicamente. Por exemplo, ao ler um arquivo linha por linha em uma rede, você pode usar ayield
função da seguinte maneira:Você pode usá-lo em seu código da seguinte maneira:
Pega de transferência de controle de execução
O controle de execução será transferido de getNextLines () para o
for
loop quando o rendimento for executado. Assim, toda vez que getNextLines () é chamado, a execução começa a partir do ponto em que foi pausada na última vez.Assim, resumindo, uma função com o seguinte código
irá imprimir
fonte
Um exemplo fácil de entender o que é:
yield
A saída é:
fonte
print(i, end=' ')
? Caso contrário, acredito que o comportamento padrão colocaria cada número em uma nova linha(Minha resposta abaixo fala apenas da perspectiva de usar o gerador Python, não da implementação subjacente do mecanismo do gerador , que envolve alguns truques de manipulação de pilha e pilha.)
Quando
yield
é usado em vez dereturn
em uma função python, essa função é transformada em algo especial chamadogenerator function
. Essa função retornará um objeto dogenerator
tipo Ayield
palavra-chave é um sinalizador para notificar o compilador python para tratar essa função especialmente. As funções normais serão encerradas assim que algum valor for retornado. Mas com a ajuda do compilador, a função do gerador pode ser considerada como recuperável. Ou seja, o contexto de execução será restaurado e a execução continuará na última execução. Até você chamar explicitamente return, o que gerará umaStopIteration
exceção (que também faz parte do protocolo iterador) ou chegará ao final da função. Eu encontrei um monte de referências sobregenerator
, mas este umdofunctional programming perspective
é o mais digerível.(Agora, quero falar sobre a lógica por trás
generator
e comiterator
base no meu próprio entendimento. Espero que isso possa ajudá-lo a entender a motivação essencial do iterador e gerador. Esse conceito aparece em outros idiomas e também em C #.)Pelo que entendi, quando queremos processar um monte de dados, geralmente armazenamos os dados em algum lugar e depois os processamos um a um. Mas essa abordagem ingênua é problemática. Se o volume de dados for grande, é caro armazená-los como um todo antecipadamente. Então, em vez de armazenar o
data
próprio diretamente, por que não armazenarmetadata
indiretamente, iethe logic how the data is computed
.Existem 2 abordagens para agrupar esses metadados.
as a class
. É o chamadoiterator
que implementa o protocolo iterador (ou seja__next__()
, os__iter__()
métodos). Esse também é o padrão de design do iterador comumente visto .as a function
. Este é o chamadogenerator function
. Mas, sob o capô, o iteradorgenerator object
imóvel retornadoIS-A
porque também implementa o protocolo do iterador.De qualquer maneira, um iterador é criado, ou seja, algum objeto que pode fornecer os dados que você deseja. A abordagem OO pode ser um pouco complexa. Enfim, qual deles usar depende de você.
fonte
Em resumo, a
yield
instrução transforma sua função em uma fábrica que produz um objeto especial chamado agenerator
que envolve o corpo da sua função original. Quandogenerator
iterado, ele executa sua função até chegar ao próximoyield
, suspende a execução e avalia o valor passado parayield
. Ele repete esse processo em cada iteração até que o caminho da execução saia da função. Por exemplo,simplesmente gera
A potência vem do uso do gerador com um loop que calcula uma sequência, o gerador executa o loop cada vez para 'produzir' o próximo resultado do cálculo, dessa forma calcula uma lista em tempo real, sendo o benefício a memória salvo para cálculos especialmente grandes
Digamos que você queira criar uma
range
função própria que produza um intervalo iterável de números, você pode fazê-lo assim,e use assim;
Mas isso é ineficiente porque
Felizmente Guido e sua equipe foram generosos o suficiente para desenvolver geradores para que pudéssemos fazer isso;
Agora, a cada iteração, uma função no gerador chamado
next()
executa a função até atingir uma instrução 'yield' na qual para e 'produz' o valor ou atinge o final da função. Nesse caso, na primeira chamada,next()
executa até a declaração de rendimento e produz 'n'; na próxima chamada, ela executará a declaração de incremento, retornará ao 'while', avaliará e, se for verdadeiro, irá parar e yield 'n' novamente, continuará assim até que a condição while retorne falsa e o gerador salte para o final da função.fonte
Rendimento é um objeto
A
return
em uma função retornará um único valor.Se você deseja que uma função retorne um conjunto enorme de valores , use
yield
.Mais importante,
yield
é uma barreira .Ou seja, ele executará o código em sua função desde o início até que seja atingido
yield
. Então, ele retornará o primeiro valor do loop.Em seguida, todas as outras chamadas executam o loop que você escreveu na função mais uma vez, retornando o próximo valor até que não haja nenhum valor a ser retornado.
fonte
Muitas pessoas usam
return
e nãoyield
, mas em alguns casosyield
podem ser mais eficientes e fáceis de trabalhar.Aqui está um exemplo que
yield
é definitivamente melhor para:Ambas as funções fazem a mesma coisa, mas
yield
usam três linhas em vez de cinco e tem uma variável a menos para se preocupar.Como você pode ver, ambas as funções fazem a mesma coisa. A única diferença é
return_dates()
fornecer uma lista eyield_dates()
gerar um gerador.Um exemplo da vida real seria algo como ler um arquivo linha por linha ou se você apenas deseja criar um gerador.
fonte
yield
é como um elemento de retorno para uma função. A diferença é que oyield
elemento transforma uma função em um gerador. Um gerador se comporta como uma função até que algo seja 'produzido'. O gerador para até a próxima chamada e continua exatamente do mesmo ponto em que foi iniciado. Você pode obter uma sequência de todos os valores 'produzidos' em um, chamandolist(generator())
.fonte
A
yield
palavra-chave simplesmente coleta os resultados retornados. Penseyield
comoreturn +=
fonte
Aqui está uma
yield
abordagem simples , para calcular a série de fibonacci, explicada:Quando você digita isso no seu REPL e tenta chamá-lo, você obtém um resultado intrigante:
Isso ocorre porque a presença de
yield
sinalizou para Python que você deseja criar um gerador , ou seja, um objeto que gera valores sob demanda.Então, como você gera esses valores? Isso pode ser feito diretamente usando a função interna
next
ou, indiretamente, alimentando-a com uma construção que consome valores.Usando a
next()
função interna, você invoca diretamente.next
/__next__
, forçando o gerador a produzir um valor:Indiretamente, se você fornecer
fib
umfor
loop, umlist
inicializador, umtuple
inicializador ou qualquer outra coisa que espere um objeto que gere / produz valores, você "consumirá" o gerador até que nenhum outro valor possa ser produzido por ele (e ele retornará) :Da mesma forma, com um
tuple
inicializador:Um gerador difere de uma função no sentido de ser preguiçoso. Isso é realizado mantendo o estado local e permitindo que você retome sempre que precisar.
Quando você invoca
fib
pela primeira vez , chamando-o:Python compila a função, encontra a
yield
palavra - chave e simplesmente retorna um objeto gerador de volta para você. Parece que não é muito útil.Quando você solicita que ele gere o primeiro valor, direta ou indiretamente, ele executa todas as instruções que encontra, até encontrar um
yield
, e retorna o valor que você forneceuyield
e pausa. Para um exemplo que melhor demonstra isso, vamos usar algumasprint
chamadas (substitua porprint "text"
if no Python 2):Agora, entre no REPL:
você tem um objeto gerador agora aguardando um comando para gerar um valor. Use
next
e veja o que é impresso:Os resultados não citados são impressos. O resultado citado é o que é retornado
yield
. Liguenext
novamente agora:O gerador lembra que foi interrompido
yield value
e continua a partir daí. A próxima mensagem é impressa e a busca pelayield
instrução em pausa é executada novamente (devido aowhile
loop).fonte