it-swarm-pt.tech

O que a palavra-chave "yield" faz?

Qual é o uso da palavra-chave yield no Python? O que isso faz?

Por exemplo, estou tentando entender esse código1:

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? É retornada uma lista? Um único elemento? É chamado de novo? Quando as chamadas subseqüentes serão interrompidas?


1. O código vem de Jochen Schulz (jrschulz), que criou uma ótima biblioteca Python para espaços métricos. Este é o link para a fonte completa: Module mspace .

8943
Alex. S.

Para entender o que yield faz, você deve entender o que gerators são. E antes que os geradores venham iterables.

Iterables

Quando você cria uma lista, você pode ler seus itens um por um. A leitura de seus itens, um por um, é chamada de 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, portanto, uma 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..." é um iterável; lists, strings, arquivos ...

Esses iteráveis ​​são úteis porque você pode lê-los o quanto quiser, mas armazena todos os valores na memória e nem sempre é isso que você quer quando tem muitos valores.

Geradores

Geradores são iteradores, uma espécie de iterável você só pode iterar uma vez. Os geradores não armazenam todos os valores na memória, eles geram os valores em tempo real:

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

É exatamente o mesmo, exceto que você usou () em vez de []. MAS, você não pode executa for i in mygenerator uma segunda vez, já que os geradores só podem ser usados ​​uma vez: eles calculam 0, depois esquecem e calculam 1, e terminam calculando 4, um por um.

Produção

yield é uma palavra-chave que é 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ê só precisará ler 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 roda. A função só retorna o objeto gerador, isso é um pouco complicado :-)

Então, seu código continuará de onde parou cada vez que for usa o gerador.

Agora a parte difícil:

A primeira vez que o for chama o objeto gerador criado a partir de sua função, ele irá executar o código em sua função desde o início até atingir yield, então retornará o primeiro valor do loop. Em seguida, cada outra chamada executará o loop que você gravou na função mais uma vez e retornará o próximo valor, até que não haja nenhum valor a ser retornado.

O gerador é considerado vazio quando a função é executada, mas não atinge mais yield. 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 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

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 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 passar por todos esses dados aninhados, mesmo que seja um pouco perigoso, já que você pode acabar com um loop infinito. Nesse caso, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) esgota todos os valores do gerador, mas while continua criando novos objetos geradores que produzirão valores diferentes dos anteriores, já que ele não é aplicado no mesmo nó.

  • O método extend() é um método de objeto de lista que espera um iterável e adiciona seus valores à lista.

Normalmente nós passamos uma lista para isso:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

Mas no seu código ele recebe um gerador, o que é bom porque:

  1. Você não precisa ler os valores duas vezes.
  2. Você pode ter muitos filhos e não quer que eles sejam 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. Python espera iterables, então ele irá trabalhar com strings, listas, tuplas e geradores! Isso é chamado de tipagem de pato e é uma das razões pela qual o Python é tão legal. Mas esta é outra história, para outra pergunta ...

Você pode parar aqui ou ler um pouco para ver um uso avançado de um gerador:

Controlando o esgotamento de um 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, useprint(corner_street_atm.__next__()) ou print(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? Corrente dois geradores? Agrupar valores em uma lista aninhada com um one-liner? 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

Iteração é um processo que implica iteráveis ​​(implementação do método __iter__()) e iteradores (implementação do método __next__()). Iterables são quaisquer objetos dos quais você pode obter um iterador. Iteradores são objetos que permitem iterar em iteráveis.

Há mais sobre isso neste artigo sobre como for loops funcionam .

13125
e-satis

A palavra-chave yield é reduzida para dois fatos simples:

  1. Se o compilador detectar a palavra-chave yieldanywhere dentro de uma função, essa função não retornará mais pela instrução return. Instead, ele imediatamente retorna um objeto "lista pendente" preguiçoso chamado de gerador
  2. Um gerador é iterável. O que é um iterável? É algo como um list ou set ou range ou dict-view, com um protocolo interno para visitar cada elemento em uma determinada ordem.

Em suma: a generator é uma lista preguiçosa, com incrementos pendentes, e yield permitem que você use a notação de função para programar os valores da lista que o gerador deveria cunhar de forma incremental.

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 makeRange que é igual a range do Python. Chamando makeRange(n) RETORNA 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 a retornar imediatamente seus valores pendentes, você pode passá-lo para list() (assim como você faria qualquer iterável):

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

Comparando o exemplo com "apenas retornando uma lista"

O exemplo acima pode ser pensado como meramente criando uma lista que você acrescenta 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]

Há uma grande diferença, no entanto; veja a última seção.


Como você pode usar geradores

Um iterável é a última parte de uma compreensão de lista, e todos os geradores são iteráveis, então eles são usados ​​da seguinte forma:

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

Para ter uma idéia melhor dos 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 lentas infinitamente longas como itertools.count(). Você poderia implementar sua própria def enumerate(iterable): Zip(count(), iterable) ou, alternativamente, fazê-lo com a palavra-chave yield em um loop while.

Por favor note: geradores podem realmente ser usados ​​para muitas outras coisas, como implementação de coroutines 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 funciona o "protocolo de iteração Python". Isto é, o que está acontecendo quando você faz list(makeRange(5)). Isto é o que eu descrevo anteriormente como uma "lista incremental preguiçosa".

>>> 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 interna next() apenas chama a função .next() dos objetos, que é parte do "protocolo de iteração" e é encontrada em todos os iteradores. Você pode usar manualmente a função next() (e outras partes do protocolo de iteração) para implementar coisas sofisticadas, geralmente à custa de legibilidade, então tente evitar fazer isso ...


Minúcias

Normalmente, a maioria das pessoas não se importaria com as seguintes distinções e provavelmente desejaria parar de ler aqui.

Em Python-speak, um iterávelé qualquer objeto que "entenda o conceito de um loop for" como uma lista [1,2,3], e um iterador é uma instância específica do for solicitado. loop como [1,2,3].__iter__(). Um gerador é exatamente igual a qualquer iterador, exceto pelo modo como foi escrito (com sintaxe de 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 apenas fornece uma cópia de si mesmo.

Assim, no caso improvável de você não estar fazendo 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 chamar myRange(...) novamente. Se você precisar usar o resultado duas vezes, converta o resultado em uma lista e armazene-o em uma variável x = list(myRange(5)). Aqueles que absolutamente precisam clonar um gerador (por exemplo, que estão fazendo metaprogramação assustadoramente hackies) podem usar itertools.tee se for absolutamente necessário, uma vez que a proposta do iterador copiável Python PEP foi adiada.

380
ninjagecko

O que a palavra-chave yield faz no Python?

Resumo da Resposta/Resumo

  • Uma função com yield , quando chamada, retorna um Generator .
  • Os geradores são iteradores porque implementam o iterator protocol , para que você possa iterá-los.
  • Um gerador também pode ser informação enviada, tornando-se conceitualmente uma coroutine.
  • No Python 3, você pode delegar de um gerador para outro em ambas as direções com yield from.
  • (Apêndice critica algumas respostas, incluindo a primeira, e discute o uso de return em um gerador.)

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 ele retorne um gerador.

A idéia de geradores vem de outras linguagens (veja a nota de rodapé 1) com implementações variadas. Nos Geradores de Python, a execução do código é congelado no ponto do rendimento. Quando o gerador é chamado (os métodos são discutidos abaixo), a execução é retomada e, em seguida, congela no próximo rendimento.

yield fornece uma maneira fácil de implementar o protocolo do iterador , definida pelos dois métodos a seguir: __iter__ e next (Python 2) ou __next__ (Python 3). Ambos os métodos Tornam um objeto um iterador que você poderia digitar com a classe Iterator Abstract Base 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 de gerador é um subtipo de iterador:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

E, se necessário, podemos verificar como este:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Um recurso de um Iteratorque esgotou uma vez , você não pode reutilizá-lo ou redefini-lo:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

Você terá que fazer outro se quiser usar sua funcionalidade novamente (consulte a nota de rodapé 2):

>>> list(func())
['I am', 'a generator!']

Um pode gerar dados programaticamente, por exemplo:

def func(an_iterable):
    for item in an_iterable:
        yield item

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 :

def func(an_iterable):
    yield from an_iterable

No entanto, yield from também permite delegação aos subgeradores, , Que serão explicados na seção seguinte sobre delegação cooperativa com sub-corrotinas.

Coroutines:

yield forma uma expressão que permite que os dados sejam enviados para o gerador (consulte a nota de rodapé 3)

Aqui está um exemplo, tome nota da variável received, que irá 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, devemos enfileirar o gerador com a função interna, next . Ele irá Chamar o método next ou __next__ apropriado, dependendo da versão do Python que você está usando:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

E agora podemos enviar dados para o gerador. ( Enviar None é O mesmo que chamar next .):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Delegação Cooperativa para Sub-Coroutine com yield from

Agora, lembre-se de que yield from está disponível no Python 3. Isso nos permite delegar Corrotinas a uma subcoração:

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 funcionalidade a um sub-gerador e pode ser usado Por um gerador, como 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 in PEP 380.

Outros métodos: fechar e jogar

O método close aumenta GeneratorExit no ponto em que a execução da função Foi congelada. Isso também será chamado por __del__ para que você Possa colocar qualquer código de limpeza onde você manipule o GeneratorExit:

>>> my_account.close()

Você também pode lançar uma exceção que pode ser manipulada no gerador Ou propagada de volta para o usuário:

>>> 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

Acredito ter coberto todos os aspectos da seguinte questão:

O que a palavra-chave yield faz no Python?

Acontece que yield faz muito. Tenho certeza de que poderia acrescentar ainda mais exemplos completos a isso. Se você quiser mais ou tiver algumas críticas construtivas, me avise comentando Abaixo.


Apêndice:

Crítica da Resposta Top/Aceito **

  • É confuso sobre o que faz um iterable, 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 pelos loops for até que ele aumente StopIteration, e uma vez feito isso, continuará a fazê-lo.
  • Em seguida, ele usa uma expressão de gerador para descrever o que é um gerador. Como um gerador é simplesmente uma maneira conveniente de criar um iterator, ele apenas confunde o assunto, e ainda não chegamos à parte yield.
  • Em Controlando a exaustão do gerador ele chama o método .next, quando ele deveria usar a função interna, next. Seria uma camada apropriada de indireção, porque seu código não funciona no Python 3.
  • Itertools? Isso não é relevante para o que yield faz.
  • Nenhuma discussão sobre os métodos que yield fornece junto com a nova funcionalidade yield from no Python 3. A resposta top/accepted é uma resposta muito incompleta.

Crítica de resposta sugerindo yield em uma expressão ou compreensão geradora.

A gramática permite atualmente qualquer expressão em uma compreensão de 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, foi considerado por alguns como 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 suspensão de sua permissão . Aqui está um post relevante da lista de discussão:

Em 30 de janeiro de 2017 às 19:05, Brett Cannon escreveu:

Dom Dom, 29 Jan 2017 at 16:39 Craig Rodrigues escreveu:

Eu estou bem com qualquer abordagem. Deixar as coisas como estão em Python 3 Não é bom, IMHO.

Meu voto é que seja um SyntaxError desde que você não está recebendo o que você espera de A sintaxe.

Eu concordo que é um lugar sensato para acabarmos, já que qualquer código Confiando no comportamento atual é realmente inteligente demais para ser Sustentável.

Em termos de chegar lá, provavelmente queremos:

  • SintaxeWarning ou DeprecationWarning in 3.7
  • Alerta Py3k em 2.7.x
  • SyntaxError in 3.8

Felicidades, Nick.

- Nick Coghlan | ncoghlan em gmail.com | Brisbane, Austrália

Além disso, há um problema pendente (10544) que parece estar apontando na direção dessa never sendo uma boa idéia (PyPy, uma implementação Python escrita em Python, já está levantando avisos de sintaxe .)

Bottom line, até que os desenvolvedores do CPython digam o contrário: Não coloque yield em uma expressão geradora ou compreensão.

A instrução return em um gerador

Em Python 2 :

Em uma função geradora, a instrução return não pode incluir um expression_list. Nesse contexto, um return simples indica que o gerador está pronto e fará com que StopIteration seja levantado.

Um expression_list é basicamente qualquer número de expressões separadas por vírgulas - essencialmente, no Python 2, você pode parar o gerador com return, mas você não pode retornar um valor.

Em Python 3

Em uma função geradora, a instrução return indica que o gerador está pronto e fará com que StopIteration seja levantado. O valor retornado (se houver) é usado como um argumento para construir StopIteration e se torna o atributo StopIteration.value.

Notas de rodapé

  1. As linguagens CLU, Sather e Icon foram referenciadas na proposta Para introduzir o conceito de geradores ao Python. A ideia geral é Que uma função pode manter o estado interno e gerar pontos de dados intermediários Sob demanda pelo usuário. Isso prometia ser superior em desempenho A outras abordagens, incluindo o encadeamento do Python , que nem está disponível em alguns sistemas.

  2. Isso significa, por exemplo, que os objetos xrange (range no Python 3) não são Iterators, mesmo que sejam iteráveis, porque podem ser reutilizados. Como listas, seus métodos __iter__ retornam objetos iteradores.

  3. yield foi originalmente introduzido como uma declaração, significando que só poderia aparecer no início de uma linha em um bloco de código. Agora yield cria uma expressão de rendimento. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Esta alteração foi proposta permite que um usuário envie dados para o gerador exatamente como pode ser recebido. Para enviar dados, um deve ser capaz de atribuí-lo a algo, e Para isso, uma declaração simplesmente não funcionará.

295
Aaron Hall

yield é igual a return - ele retorna o que você quiser (como um gerador). A diferença é que na próxima vez que você chamar o gerador, a execução será iniciada da última chamada para a instrução yield. Ao contrário de return, 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 em que a função for chamada

No caso do seu código, a função get_child_candidates está agindo como um iterador para que, quando você estender sua lista, adicione um elemento de cada vez à nova lista.

list.extend chama um iterador até que esteja esgotado. No caso da amostra de código que você postou, seria muito mais claro apenas retornar um Tuple e anexá-lo à lista.

260
Douglas Mayle

Há uma coisa extra para mencionar: uma função que produz realmente não precisa terminar. Eu escrevi código assim:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Então eu posso usá-lo em outro código como este:

for f in fib():
    if some_condition: break
    coolfuncs(f);

Isso realmente ajuda a simplificar alguns problemas e facilita o trabalho com algumas coisas. 

199
Claudiu

Para aqueles que preferem um exemplo mínimo de trabalho, medite nesta sessão interativa 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
171
Daniel

TL; DR

Em vez disso:

def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

faça isso:

def square_yield(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

Sempre que você se encontrar construindo uma lista a partir do zero, yield cada uma delas. 

Este foi o meu primeiro momento "aha" com rendimento.


yield é um açucarado maneira de dizer 

construir uma série de coisas

Mesmo comportamento:

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

Comportamento diferente:

O rendimento é passagem única: você só pode percorrer uma vez. Quando uma função tem um rendimento, nós a chamamos de uma função generator . E um iterador é o que ele retorna. Esses termos são reveladores. Perdemos a conveniência de um contêiner, mas ganhamos o poder de uma série calculada conforme a necessidade e arbitrariamente longa.

O rendimento é lazy, adia a computação. Uma função com um rendimento na verdade não é executada quando você a chama Retorna um objeto iterator que lembra de onde parou. Cada vez que você chama next() no iterador (isso acontece em um loop forçado), a execução da polegada avança para o próximo rendimento. return aumenta StopIteration e finaliza a série (este é o final 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.

>>> 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 você precisar de vários passes e a série não for muito longa, apenas chame list() nela:

>>> list(square_yield(4))
[0, 1, 4, 9]

Escolha brilhante da palavra yield porque ambos os significados apply:

yield - produzir ou fornecer (como na agricultura)

... forneça os próximos dados da série.

yield - ceder ou desistir (como no poder político)

... abdicar da execução da CPU até que o iterador avance.

160
Bob Stein

O rendimento lhe dá um gerador. 

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 você pode ver, no primeiro caso, foo mantém toda a lista na memória de uma só vez. Não é um grande problema para uma lista com 5 elementos, mas e se você quiser uma lista de 5 milhões? Não só isso é um enorme comedor de memória, também custa muito tempo para construir no momento em que a função é chamada. No segundo caso, a barra apenas fornece um gerador. Um gerador é iterável - o que significa que você pode usá-lo em um loop for, etc, mas cada valor só pode ser acessado 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 loop da última vez que você o chamou - assim, se você estiver usando um iterável para (digamos) contar até 50 bilhões, você não precisa contar até 50 bilhões de uma só vez e armazenar os 50 bilhões de números para contar. Mais uma vez, este é um exemplo bastante artificial, você provavelmente usaria os instrumentos se realmente quisesse contar até 50 bilhões. :)

Este é o caso de uso mais simples dos geradores. Como você disse, ele pode ser usado para escrever permutações eficientes, usando o rendimento para empurrar as coisas através da pilha de chamadas em vez de usar algum tipo de variável de pilha. Os geradores também podem ser usados ​​para percursos especializados em árvores e todas as outras coisas.

148
RBansal

Está devolvendo um gerador. Eu não estou particularmente familiarizado com o Python, mas acredito que seja o mesmo tipo de blocos de iteradores do C # se você estiver familiarizado com eles.

A ideia chave é que o compilador/intérprete/o que faz algum truque para que, no que diz respeito ao chamador, eles possam continuar chamando next () e ele continuará retornando valores - como se o método 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 você se lembrar de onde você está e quais são as variáveis ​​locais, etc. Isso é muito mais fácil do que escrever um iterador sozinho.

145
Jon Skeet

Há um tipo de resposta que ainda não me foi dado, entre as muitas ótimas respostas que descrevem como usar geradores. Aqui está a resposta da teoria da linguagem de programação:

A instrução yield no Python retorna um gerador. Um gerador em Python é uma função que retorna continuações (e especificamente um tipo de co-rotina, mas as 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 elas não são usadas com frequência, porque são extremamente difíceis de raciocinar e também muito difíceis de implementar. Mas a ideia 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 e assim por diante são salvos. Então, em algum momento mais adiante no programa, a continuação pode ser invocada, de tal forma que as variáveis ​​do programa são redefinidas para esse estado e as operações que foram salvas são executadas.

Continuações, nesta forma mais geral, podem ser implementadas de duas maneiras. No modo call/cc, a pilha do programa é literalmente salva e, quando a continuação é invocada, a pilha é restaurada.

No continuation passing style (CPS), as continuações são apenas funções normais (somente em linguagens onde as funções são de primeira classe) que o programador gerencia explicitamente e passa para as sub-rotinas. Nesse estilo, o estado do programa é representado por closures (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 invocando-as simplesmente chamando-as e retornando posteriormente. Um exemplo muito simples de estilo de passagem de continuação é 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 exemplo (muito simplista), o programador salva a operação de realmente gravar o arquivo em uma continuação (que pode potencialmente ser uma operação muito complexa com muitos detalhes para escrever) e, em seguida, passa essa continuação (ou seja, como um primeiro fechamento de classe) para outro operador que faz mais algum processamento e, em seguida, chama, se necessário. (Eu uso muito esse padrão de design na programação da GUI real, seja porque ele me salva linhas de código ou, mais importante, para gerenciar o fluxo de controle após o disparo dos eventos da GUI.)

O resto deste post irá, 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. Considerando que continuações são capazes, em geral, de salvar o estado de um cálculo (isto é, a pilha de chamadas do programa), geradores só são capazes de salvar o estado de iteração sobre um iterador. Embora essa definição seja um pouco enganosa para certos casos de uso de geradores. Por exemplo:

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 (ou seja, for x in collection: do_something(x)). Este exemplo ilustra o poder dos geradores: se alguma coisa é 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. Isto significa que as continuações são mais poderosas que os geradores, mas também que os geradores são muito mais fáceis. Eles são mais fáceis para o projetista de linguagem implementar, e eles são mais fáceis para o programador usar (se você tiver algum tempo para gravar, tente ler e entender esta página sobre continuações e chamar/cc ).

Mas você poderia facilmente implementar (e conceitualizar) geradores como um caso simples e específico de estilo de passagem de continuação:

Sempre que yield é chamado, ele diz à função para retornar uma continuação. Quando a função é chamada novamente, começa de onde parou. Portanto, no pseudo-pseudocódigo (ou seja, não no pseudocódigo, mas não no código), o método next do gerador é basicamente o seguinte:

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 yield é realmente açúcar sintático para a função de gerador real, basicamente algo como:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

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 usar a palavra-chave yield.

134
aestrivex

Aqui está um exemplo em linguagem simples. Eu fornecerei uma correspondência entre conceitos humanos de alto nível para conceitos de Python de baixo nível.

Eu quero operar em uma sequência de números, mas não quero me incomodar com a criação dessa sequência, quero apenas focar na operação que quero fazer. Então, eu faço o seguinte:

  • Eu te chamo e te digo que eu quero uma sequência de números que é produzida de uma maneira específica, e eu deixo você saber qual é o algoritmo. 
    Este passo corresponde a defining da função geradora, isto é, a função que contém um yield.
  • Algum tempo depois, eu lhe digo: "OK, prepare-se para me contar a sequência de números". 
    Este passo corresponde a chamar a função geradora que retorna um objeto gerador. Note que você não me conta nenhum número ainda; você acabou de pegar seu papel e lápis.
  • Eu lhe pergunto "diga-me o próximo número", e você me diz o primeiro número; depois disso, você espera que eu lhe peça o próximo número. O seu trabalho é lembrar onde você estava, que números você já disse e qual é o próximo número. Eu não me importo com os detalhes. 
    Este passo corresponde a chamar .next() no objeto gerador.
  • … Repita o passo anterior, até…
  • eventualmente, você pode chegar ao fim. Você não me conta um número; você apenas grita, "segure seus cavalos! Estou pronto! Não há mais números!" 
    Esta etapa corresponde ao objeto gerador que finaliza seu trabalho e gera uma exceção StopIteration A função de gerador não precisa levantar a exceção. É gerado 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 inicia a execução, faz uma pausa sempre que faz um yield e, quando solicitado para um valor .next(), continua do ponto em que foi o último. Ele se encaixa perfeitamente por design com o protocolo do iterador do Python, que descreve como solicitar valores sequencialmente.

O usuário mais famoso do protocolo do iterador é o comando for no Python. Então, sempre que você fizer um:

for item in sequence:

não importa se sequence é uma lista, uma string, um dicionário ou um gerador object como descrito acima; o resultado é o mesmo: você lê itens de uma sequência, um por um.

Note que defining uma função que contém uma palavra-chave yield não é a única maneira de criar um gerador; é a maneira mais fácil de criar um.

Para obter informações mais precisas, leia sobre tipos de iteradores , a declaração yield e geradores na documentação do Python.

120
tzot

Embora muitas respostas mostrem por que você usaria um yield para criar um gerador, há mais usos para yield. É muito fácil fazer uma co-rotina, o 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 entender o que um yield faz no código a seguir, você pode usar o dedo para rastrear o ciclo por meio de qualquer código que tenha um yield. Toda vez que seu dedo atinge o yield, você tem que esperar que um next ou um send sejam inseridos. Quando um next é chamado, você rastreia o código até atingir o yield… o código à direita do yield é avaliado e retornado ao responsável pela chamada… e então você espera. Quando next é chamado novamente, você executa outro loop através do código. No entanto, você notará que em uma co-rotina, yield também pode ser usado com um send… que enviará um valor do chamador para a função de retorno. Se um send for dado, então yield recebe o valor enviado e o expulsa do lado esquerdo… então o rastreamento pelo 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()
105
Mike McKerns

Existe outro uso e significado yield (desde o Python 3.3):

yield from <expr>

De PEP 380 - Sintaxe para Delegar a um Subgerador:

Uma sintaxe é proposta para um gerador delegar parte de suas operações para outro gerador. Isso permite que uma seção de código contendo 'yield' seja fatorada e colocada em outro gerador. Além disso, o subgerador pode retornar com um valor e o valor é disponibilizado para o gerador de delegação.

A nova sintaxe também abre algumas oportunidades de otimização quando um gerador gera valores produzidos por outro.

Além disso this irá introduzir (desde o Python 3.5):

async def new_coroutine(data):
   ...
   await blocking_action()

para evitar que corrotinas sejam confundidas com um gerador regular (hoje em dia yield é usado em ambos).

97
Sławomir Lenart

Todas ótimas respostas, porém um pouco difíceis para iniciantes.

Eu suponho que você tenha aprendido a instrução return.

Como analogia, return e yield são gêmeos. return significa 'return and stop', enquanto 'yield' significa 'return, but continue'

  1. Tente obter uma num_list com return.
def num_list(n):
    for i in range(n):
        return i

Executá-lo:

In [5]: num_list(3)
Out[5]: 0

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.

  1. Lá vem yield

Substitua 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 você ganha para obter todos os números.

Comparando return que é executado uma vez e pára, yield executa vezes que você planejou. Você pode interpretar return como return one of them e yield como return all of them. Isso é chamado de iterable.

  1. Mais um passo, podemos reescrever a instrução yield com return
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 núcleo sobre yield.

A diferença entre uma saída de return e a saída yield do objeto é:

Você sempre obterá [0, 1, 2] de um objeto de lista, mas somente poderá recuperá-los de 'o objeto yield saída' uma vez. Então, ele tem um novo nome generator objeto como exibido em Out[11]: <generator object num_list at 0x10327c990>.

Em conclusão, como uma metáfora para o grok:

  • return e yield são gêmeos
  • list e generator são gêmeos
86
JawSaw

Aqui estão alguns exemplos de Python de como implementar geradores como se o Python não fornecesse açúcar sintático para eles:

Como um gerador 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))

Usando fechamentos 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)

Usando fechamento de objetos ao invés de geradores (porque ClosuresAndObjectsAreEquivalent )

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)
83
Dustin Getz

Eu ia postar "leia a página 19 de" 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 corrotinas como o dual de seu uso em funções geradoras. Embora não seja o mesmo uso que seu trecho 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 o método send(), a co-rotina será executada até que a próxima instrução (yield) seja encontrada.

Geradores e corrotinas são uma maneira legal de configurar aplicativos do tipo de fluxo de dados. Eu pensei que valeria a pena saber sobre o outro uso da instrução yield em funções.

81
johnzachary

Do ponto de vista de 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), um usa mensagens enviadas para um objeto de fechamento, que possui um dispatcher, e o dispatcher responde a "mensagens".

http://en.wikipedia.org/wiki/Message_passing

"next" é uma mensagem enviada para um encerramento, criada pela chamada "iter".

Existem muitas maneiras de implementar esse cálculo. Eu usei a mutação, mas é fácil fazê-lo sem mutação, retornando o valor atual e o próximo yielder.

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 alteração na sintaxe é necessária para reescrevê-lo no 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
->
77
alinsoar

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)

Saída:

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 que 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á esperando nessa posição e, pouco antes disso, retornando um valor para fora e a próxima vez continua funcionando.

Parece ser uma habilidade interessante e agradável: D

70
Engin OZTURK

Aqui está uma imagem mental do que yield faz.

Eu gosto de pensar em um thread como tendo uma pilha (mesmo quando não é implementado dessa forma).

Quando uma função normal é chamada, ela coloca suas variáveis ​​locais na pilha, faz algum cálculo, então limpa a pilha e retorna. Os valores de suas variáveis ​​locais nunca são vistos novamente.

Com uma função yield, quando seu código começa a ser executado (ou seja, depois que a função é chamada, retornando um objeto gerador, cujo método next() é invocado), ele coloca suas variáveis ​​locais na pilha e calcula por um tempo. Mas então, quando atinge a instrução yield, antes de limpar sua parte da pilha e retornar, ela pega um instantâneo de suas variáveis ​​locais e as armazena no objeto gerador. Ele também escreve o local onde está atualmente em seu código (ou seja, a instrução yield particular).

Então é uma espécie de função congelada que o gerador está pendurado.

Quando next() é chamado posteriormente, ele recupera os pertences da função na pilha e a re-anima. A função continua a calcular de onde parou, alheia ao fato de que ela havia passado uma eternidade no armazenamento a 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 maneira muito diferente da primeira. A instrução yield pode estar inacessível, mas se ela estiver presente em qualquer lugar, ela mudará a natureza do que estamos tratando.

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Chamar yielderFunction() não executa seu código, mas gera um gerador fora do código. (Talvez seja uma boa idéia nomear essas coisas com o prefixo yielder para facilitar a leitura.)

>>> 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 é armazenado. Explorando-os com dir(..), podemos confirmar que nosso modelo mental acima é credível.

58
Evgeni Sergeev

Como toda resposta sugere, yield é usado para criar um gerador de seqüência. É usado para gerar alguma sequência dinamicamente. Por exemplo, ao ler um arquivo linha a linha em uma rede, você pode usar a função yield da seguinte maneira:

def getNextLines():
   while con.isOpen():
       yield con.read()

Você pode usá-lo em seu código da seguinte maneira:

for line in getNextLines():
    doSomeThing(line)

Execution Control Transfer pegadinha

O controle de execução será transferido de getNextLines () para o loop for quando o rendimento for executado. Assim, toda vez que getNextLines () é invocado, a execução começa a partir do ponto em que foi pausada 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

vai imprimir

"first time"
"second time"
"third time"
"Now some useful value 12"
49
Mangu Singh Rajpurohit

O rendimento é um objeto

Um return em uma função retornará um único valor.

Se você quiser uma função para retornar um conjunto enorme de valores , use yield.

Mais importante, yield é uma barreira.

como barreira na linguagem CUDA, ela não transferirá o controle até que ele seja concluído .

Ou seja, ele executará o código em sua função desde o início até atingir yield. Então, ele retornará o primeiro valor do loop.

Em seguida, todas as outras chamadas executarão o loop que você gravou na função mais uma vez, retornando o próximo valor até que não haja nenhum valor a ser retornado.

43
Kaleem Ullah

Em resumo, a instrução yield transforma sua função em uma fábrica que produz um objeto especial chamado generator, que envolve o corpo de sua função original. Quando a generator é iterada, ela executa sua função até atingir a próxima yield e depois suspende a execução e avalia o valor passado para yield. Ele repete esse processo em cada iteração até que o caminho da 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 do uso do gerador com um loop que calcula uma sequência, o gerador executa o loop parando cada vez para 'render' o próximo resultado do cálculo, desta forma ele calcula uma lista na hora, sendo o benefício a memória salvo para cálculos especialmente grandes

Digamos que você queira criar sua própria função range, que produz um intervalo iterável de números, você pode fazer assim,

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

e usá-lo assim;

for i in myRangeNaive(10):
    print i

Mas isso é ineficiente porque

  • Você cria uma matriz que você usa apenas uma vez (isso desperdiça memória)
  • Este código, na verdade, faz um loop sobre esse array duas vezes! :(

Felizmente Guido e sua equipe foram generosos o suficiente para desenvolver geradores para que pudéssemos fazer isso;

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é que ela atinja uma instrução 'yield' na qual ela pára e 'rende' o valor ou atinge o final da função. Neste caso, na primeira chamada, next() executa até a instrução yield e produz 'n', na próxima chamada executará a instrução de incremento, retornará ao 'while', avaliá-lo e, se true, parará e ceder 'n' novamente, continuará assim até que a condição while retorne false e o gerador pule para o final da função.

42
redbandit

Muitas pessoas usam return em vez de yield, mas em alguns casos yield pode ser mais eficiente e mais fácil de se 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.

Chamando funções

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 variável a menos para se preocupar.

Este é o resultado do código:

Output

Como você pode ver as duas funções fazem a mesma coisa. A única diferença é que return_dates() dá uma lista e yield_dates() dá um gerador.

Um exemplo da vida real seria algo como ler um arquivo linha por linha ou se você quer apenas fazer um gerador.

40
Tom Fuller

yield é como um elemento de retorno para uma função. A diferença é que o elemento yield transforma uma função em um gerador. Um gerador se comporta como uma função até que algo seja "produzido". O gerador para até ser chamado e continua exatamente do mesmo ponto em que começou. Você pode obter uma seqüência de todos os valores 'yielded' em um, chamando list(generator()).

35
Theoremiser

A palavra-chave yield simplesmente coleta resultados de retorno. Pense em yield como return +=

35
Bahtiyar Özdere

Aqui está uma abordagem baseada em yield simples, para calcular a série de fibonacci, explicou:

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

Quando você insere isso no seu REPL e tenta chamá-lo, você obterá um resultado mistificador:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

Isso ocorre porque a presença de yield sinalizou para o Python que você deseja criar um generator , 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 em uma construção que consome valores. 

Usando a função next() incorporada, você chama diretamente .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 para um loop for, um inicializador list, um inicializador Tuple ou qualquer outra coisa que espere um objeto que gere/produza valores, você "consumirá" o gerador até que nenhum outro valor possa ser produzido por ele. e retorna):

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. Isso é feito mantendo-se o estado local e permitindo que você reinicie sempre que precisar. 

Quando você chama primeiro fib chamando-o:

f = fib()

O Python compila a função, encontra a palavra-chave yield e simplesmente retorna um objeto gerador de volta para você. Não é muito útil parece. 

Quando você solicita que ele gere o primeiro valor, direta ou indiretamente, ele executa todas as declarações que encontrar, até encontrar uma yield, então retorna o valor que você forneceu para yield e faz uma pausa. Para um exemplo que melhor demonstra isso, vamos usar algumas chamadas print (substitua por print "text" se estiver no 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, entre no REPL:

>>> gen = yielder("Hello, yield!")

você tem um objeto gerador agora aguardando 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 é impresso. O resultado citado é o que é retornado de yield. Ligue novamente para next 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 que ele foi pausado em yield value e continua a partir daí. A próxima mensagem é impressa e a busca pela instrução yield para pausar nela é executada novamente (devido ao loop while).

32
Jim Fasarakis Hilliard

Um exemplo simples do que é facilmente explicado: yield

def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print i

A saída é: 

1 2 1 2 1 2 1 2
29
Gavriel Cohen

Ainda outro TL; DR

Iterador na lista: next() retorna o próximo elemento da lista

Iterator generator: next() irá calcular o próximo elemento na hora (executar código)

Você pode ver o rendimento/gerador como uma maneira de executar manualmente o control flow a partir do exterior (como continue loop one step), chamando next, porém complexo o fluxo.

Nota: O gerador é N&ATILDE;O uma função normal. Ele lembra o estado anterior como variáveis ​​locais (pilha). Veja outras respostas ou artigos para uma explicação detalhada. O gerador só pode ser iterado em uma vez. Você poderia fazer sem yield, mas não seria tão agradável, então pode ser considerado um açúcar muito agradável.

28
Christophe Roussy

rendimento é semelhante ao retorno. A diferença é: 

yield torna uma função iterável (no exemplo a seguir, a função primes(n = 1) se torna iterável).
O que essencialmente significa é que da próxima vez que a função for chamada, ela continuará de onde ela saiu (que é depois da linha de yield expression).

def isprime(n):
    if n == 1:
        return False
    for x in range(2, n):
        if n % x == 0:
            return False
    else:
        return True

def primes(n = 1):
   while(True):
       if isprime(n): yield n
       n += 1 

for n in primes():
    if n > 100: break
    print(n)

No exemplo acima, se isprime(n) for true, retornará o número primo. Na próxima iteração, continuará da próxima linha 

n += 1  
24
blueray

Todas as respostas aqui são ótimas; mas apenas um deles (o mais votado) se relaciona com como seu código funciona . Outros estão relacionados com generators em geral, e como eles funcionam.

Então não vou repetir o que os geradores são ou o que os rendimentos fazem; Eu acho que estas são cobertas por grandes respostas existentes. No entanto, depois de passar algumas horas tentando entender um código semelhante ao seu, vou ver como isso funciona.

Seu código percorre uma estrutura em árvore binária. Vamos pegar essa árvore por exemplo:

    5
   / \
  3   6
 / \   \
1   4   8

E outra implementação mais simples de um caminho de árvore de busca binária:

class Node(object):
..
def __iter__(self):
    if self.has_left_child():
        for child in self.left:
            yield child

    yield self.val

    if self.has_right_child():
        for child in self.right:
            yield child

O código de execução está no objeto Tree, que implementa __iter__ como este:

def __iter__(self):

    class EmptyIter():
        def next(self):
            raise StopIteration

    if self.root:
        return self.root.__iter__()
    return EmptyIter()

A instrução while candidates pode ser substituída por for element in tree; Python traduz isso para

it = iter(TreeObj)  # returns iter(self.root) which calls self.root.__iter__()
for element in it: 
    .. process element .. 

Como a função Node.__iter__ é um gerador, o código dentro dela é executado por iteração. Então a execução ficaria assim:

  1. elemento raiz é o primeiro; verifique se deixou childs e for iterar-los (vamos chamá-lo1 porque é o primeiro objeto iterador)
  2. tem um filho, então o for é executado. O for child in self.left cria um new iterator de self.left, que é um objeto Node em si (it2)
  3. Mesma lógica que 2, e uma nova iterator é criada (it3)
  4. Agora chegamos à extremidade esquerda da árvore. it3 não tem filhos de esquerda, por isso continua e yield self.value
  5. Na próxima chamada para next(it3) ele aumenta StopIteration e existe uma vez que não tem childs certos (chega ao final da função sem produzir nada)
  6. it1 e it2 ainda estão ativos - eles não estão esgotados e chamar next(it2) resultaria em valores, e não aumentaria StopIteration
  7. Agora estamos de volta ao contexto it2, e chamamos next(it2) que continua onde parou: logo após a instrução yield child. Como não tem mais filhos da esquerda, continua e produz self.val.

A captura aqui é que toda iteração cria sub-iteradores para atravessar a árvore e mantém o estado do iterador atual. Quando ele atinge o final, ele volta a pilha e os valores são retornados na ordem correta (o menor valor de produção primeiro).

Seu exemplo de código fez algo semelhante em uma técnica diferente: ele preencheu uma lista um elemento para cada filho, então na próxima iteração aparece e executa o código da função no objeto atual (daí a self).

Espero que isso tenha contribuído um pouco para esse tópico lendário. Passei várias horas desenhando esse processo para compreendê-lo.

10
Chen A.

Em suma, o uso de yield é semelhante à palavra-chave return, exceto que retorna um generator .
Um objeto generator atravessa apenas once.

yield tem dois benefícios: 

  1. Você não precisa ler esses valores duas vezes; 
  2. Você pode obter muitos nós filhos sem colocá-los todos na memória.
8
123

Uma analogia poderia ajudar a entender a ideia aqui:

Imagine que você criou uma máquina incrível capaz de gerar milhares e milhares de lâmpadas por dia. A máquina gera essas lâmpadas em caixas com um número de série exclusivo. Você não tem espaço suficiente para armazenar todas essas lâmpadas ao mesmo tempo (ou seja, você não consegue acompanhar a velocidade da máquina devido à limitação de armazenamento), portanto, você gostaria de ajustar essa máquina para gerar lâmpadas sob demanda.

Geradores Python não diferem muito desse conceito.

Imagine que você tenha uma função x que gere números de série exclusivos para as caixas. Obviamente, você pode ter um número muito grande de códigos de barras gerados pela função. Uma opção mais inteligente e eficiente em termos de espaço é gerar esses números de série sob demanda.

Código da máquina:

def barcode_generator():
    serial_number = 10000  # Initial barcode
    while True:
        yield serial_number
        serial_number += 1


barcode = barcode_generator()
while True:
    number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
    barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
    print(barcodes)

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

    produce_more = input("Produce more? [Y/n]: ")
    if produce_more == "n":
        break

Como você pode ver, temos uma "função" autônoma para gerar o próximo número de série exclusivo a cada vez. Esta função retorna um gerador! Como você pode ver, não estamos chamando a função toda vez que precisamos de um novo número de série, mas estamos usando next() dado o gerador para obter o próximo número de série.

Saída:

How many lightbulbs to generate? 5
[10000, 10001, 10002, 10003, 10004]
Produce more? [Y/n]: y
How many lightbulbs to generate? 6
[10005, 10006, 10007, 10008, 10009, 10010]
Produce more? [Y/n]: y
How many lightbulbs to generate? 7
[10011, 10012, 10013, 10014, 10015, 10016, 10017]
Produce more? [Y/n]: n
8
Rafael

Em Python, generators (um tipo especial de iterators) é usado para gerar séries de valores e yield palavra-chave é como a palavra-chave return das funções do gerador. 

A outra coisa fascinante que a palavra-chave yield faz é salvar o state de uma função geradora

Então, podemos definir um number para um valor diferente a cada vez que o generator resulta. 

Aqui está uma instância:

def getPrimes(number):
    while True:
        if isPrime(number):
            number = yield number     # a miracle occurs here
        number += 1

def printSuccessivePrimes(iterations, base=10):
primeGenerator = getPrimes(base)
primeGenerator.send(None)
for power in range(iterations):
    print(primeGenerator.send(base ** power))
7
ARGeo

Produção

>>> def create_generator():
...    my_list = range(3)
...    for i in my_list:
...        yield i*i
...
>>> my_generator = create_generator() # create a generator
>>> print(my_generator) # my_generator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in my_generator:
...     print(i)
0
1
4

Em suma , você pode ver que o loop não para e continua a funcionar mesmo após o objeto ou variável ser enviado (ao contrário de return onde o loop pára após a execução).

6
Gavriel Cohen

yield É um tipo de gerador que pode ser usado em python.

aqui está um link para ver o que o Yield realmente faz, também em geração. Geradores e Yield Keyword - Python Central (PC)

Também yield funciona como return, mas de uma maneira diferente de return. Mesmo há um link que explica yield mais, se você não entender o outro não tão bem. Melhore sua habilidade de rendimento - jeffknupp

3
PIZZZZZZZZZZZA is here

Em palavras mais simples, 'yield' é semelhante a 'return' um valor, mas funciona no Generator.

1
user3701435

Em yield simples retorna o objeto gerador em vez de valores. 

Abaixo exemplo simples ajudará!

def sim_generator():
    for i in range(3):
        yield(i)

obj = sim_generator()
next(obj) # another way is obj.__next__()
next(obj)
next(obj)

o código acima retorna 0, 1, 2

ou até mesmo curto

for val in sim_generator():
    print(val)

retornar 0, 1, 2

Espero que isto ajude

1
Vivek Ananthan

Uma simples função geradora

def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

yield statement pausa a função salvando todos os seus estados e depois continua a partir de sucessivas chamadas.

https://www.programiz.com/python-programming/generator

0
Savai Maheshwari

yield produz alguma coisa. É como se alguém lhe pedisse para fazer 5 cup cakes. Se você terminar com pelo menos um bolo de xícara, você pode dar a eles para comer enquanto você faz outros bolos.

In [4]: def make_cake(numbers):
   ...:     for i in range(numbers):
   ...:         yield 'Cake {}'.format(i)
   ...:

In [5]: factory = make_cake(5)

Aqui factory é chamado de gerador, o que faz você bolos. Se você chamar make_function, você receberá um gerador em vez de executar essa função. É porque quando a palavra-chave yield está presente em uma função, ela se torna um gerador.

In [7]: next(factory)
Out[7]: 'Cake 0'

In [8]: next(factory)
Out[8]: 'Cake 1'

In [9]: next(factory)
Out[9]: 'Cake 2'

In [10]: next(factory)
Out[10]: 'Cake 3'

In [11]: next(factory)
Out[11]: 'Cake 4'

Eles consumiram todos os bolos, mas pediram um novamente.

In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)

StopIteration:

e eles estão sendo instruídos a parar de perguntar mais. Então, uma vez que você consumiu um gerador, você está acabado. Você precisa ligar para make_cake novamente se quiser mais bolos. É como colocar outro pedido para cup cakes.

In [13]: factory = make_cake(3)

In [14]: for cake in factory:
    ...:     print(cake)
    ...:
Cake 0
Cake 1
Cake 2

Você também pode usar loop com um gerador como o acima.

Mais um exemplo: Digamos que você queira uma senha aleatória sempre que a solicitar.

In [22]: import random

In [23]: import string

In [24]: def random_password_generator():
    ...:     while True:
    ...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
    ...:

In [25]: rpg = random_password_generator()

In [26]: for i in range(3):
    ...:     print(next(rpg))
    ...:
FXpUBhhH
DdUDHoHn
dvtebEqG

In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

Aqui rpg é um gerador, que pode gerar um número infinito de senhas aleatórias. Assim, podemos também dizer que os geradores são úteis quando não sabemos o comprimento da sequência, ao contrário da lista, que possui um número finito de elementos.

0
thavan