O que faz a palavra-chave" yield"?

Qual é a utilidade da palavra-chave yield em Python? O que faz?

Por exemplo, estou a tentar entender este código.1:

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  
E este é o chamador.
result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

o que acontece quando o método _get_child_candidates é chamado? A lista foi devolvida? Um único elemento? Chama-se outra vez? Quando é que as chamadas posteriores vão parar?


1. O código vem de Jochen Schulz (jrschulz), que fez uma grande biblioteca Python para espaços métricos. Este é o ligação à fonte completa: Módulo Mspace .

Author: linusg, 2008-10-24

30 answers

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

Iterables

Quando se cria uma lista, pode-se ler os seus itens um a um. Ler os seus itens um a um chama-se iteração:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist é um iterável . Quando você usa uma compreensão de lista, você cria uma lista, e assim uma iterável:

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

Tudo o que se pode usar em "for... in..." é iterável.; lists, strings, ficheiros...

Estes iterables são úteis porque você pode lê-los o quanto quiser, mas você armazena todos os valores na memória e isso não é sempre o que você quer quando você tem um monte de valores.

Geradores

Os geradores são iteradores, uma espécie de iterável. Os geradores não armazenam todos os valores na memória, geram os valores na altura:
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

É a mesma coisa excepto que usaste () em vez de []. Mas, você Não pode executar for i in mygenerator uma segunda vez, uma vez que os geradores só podem ser usados uma vez: eles calculam 0, em seguida, esqueça sobre isso e calcular 1, e terminar o cálculo 4, um por um.

Rendimento

yield é uma palavra-chave que é usada como return, exceto que a função irá retornar um gerador.

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4
Aqui está um exemplo inútil, mas é útil quando se sabe que a função irá devolver um enorme conjunto de valores que só terá de ler. quando.

Para mestre yield, Você deve entender que quando você chama a função, o código que você escreveu no corpo da função não funciona. a função só devolve o objecto gerador, isto é um pouco complicado: -)

Então, o seu código será executado cada vez que o gerador for usado.

Agora a parte difícil:

A primeira vez que o for chama o objecto gerador criado a partir da sua função, ele irá executar o código na sua função a partir do começando até atingir yield, depois devolve o primeiro valor do laço. Então, cada outra chamada irá executar o loop que você escreveu na função mais uma vez, e retornar o próximo valor, até que não há nenhum valor para retornar.

O gerador é considerado vazio uma vez que a função funciona, mas não atinge yield mais. Pode ser porque o ciclo tinha chegado a um fim, ou porque você não satisfaz mais um "if/else".


O teu código explicado

Gerador:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Ouvinte:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

Este código contém várias partes inteligentes:

  • O loop itera em uma lista, mas a lista se expande enquanto o loop está sendo iterado :-) é uma maneira concisa de percorrer todos esses dados aninhados, mesmo que seja um pouco perigoso, uma vez que você pode acabar com um loop infinito. Neste caso, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) esgota todos os valores do gerador, mas while continua a criar novos objectos geradores que irão produzir valores diferentes dos anteriores, uma vez que não é aplicado no mesmo nó.

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

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 tem um gerador, o que é bom porque ...
    Não precisas de ler os valores duas vezes. Pode ter muitos filhos e não os quer todos armazenados em memoria.

E funciona porque o Python não se importa se o argumento de um método é uma lista ou não. O Python espera iterables para que funcione com strings, listas, tuplas e geradores! Isto é chamado de datilografia de pato e é uma das razões Por Que Python é tão legal. Mas esta é outra história, para outra pergunta...

Pode parar aqui, ou ler um pouco para ver um uso avançado de um gerador:

Controlar um gerador esgotamento

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

Nota: para o Python 3, Utilizar print(corner_street_atm.__next__()) ou print(next(corner_street_atm))

Pode ser útil para várias coisas, como controlar o acesso a um recurso. Itertools, o teu melhor amigo. O módulo itertools contém funções especiais para manipular iterables. Já desejaste duplicar um gerador? Corrente dois geradores? Valores de grupo numa lista aninhada com um liner? 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)]

Compreender os mecanismos internos da iteração

Iteração é um processo que implica iterables (implementando o método __iter__()) e iteradores (implementando o método __next__()). Iterables são todos os objectos a quem se pode arranjar um iterador. Os iteradores são objectos que te deixam entrar nos iterables.

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

 12493
Author: e-satis, 2018-05-20 09:49:28

Atalho para Grokking yield

Quando vir uma função com yield declarações, aplique este truque fácil para entender o que vai acontecer:

  1. inserir uma linha result = [] no início da função.
  2. substituir cada yield expr por result.append(expr).
  3. inserir uma linha return result na parte inferior da função.
  4. Chega de declarações! Lê e descobre o código.
  5. Compare a função com a definição original.
Este truque pode dar-lhe uma idéia da lógica por trás da função, mas o que realmente acontece com yield é significativamente diferente do que acontece na abordagem baseada na lista. Em muitos casos, o yield approach será muito mais eficiente em memória e mais rápido também. Em outros casos, este truque vai colocá-lo preso em um loop infinito, mesmo que a função original funciona muito bem. Leia mais para saber mais... Não confundas os teus Iterables, iteradores e geradores. Primeiro, o iterador. protocolo - quando escrever
for x in mylist:
    ...loop body...

O Python executa as seguintes duas etapas:

  1. Arranja um iterador para mylist:

    Call iter(mylist) - > isto devolve um objecto com um método next() (ou __next__() no Python 3).

    Este é o passo que a maioria das pessoas se esquece de te contar.
  2. Usa o iterador para repetir os itens:

    Continue chamando o método next() no iterador retornado do Passo 1. O valor de retorno de next() é atribuído a x e o corpo do laço é executado. Se uma exceção StopIteration é levantada a partir de dentro next(), significa que não há mais valores no iterador e o laço é saído.

A verdade é que o Python executa os dois passos acima sempre que quiser fazer um loop sobre o conteúdo de um objecto - por isso pode ser um loop for, mas também pode ser um código como otherlist.extend(mylist) (onde otherlist é uma lista em Python).

Aqui mylist é um iterável porque implementa o protocolo iterator. Numa classe definida pelo utilizador, pode implementar o método __iter__() para tornar as instâncias da sua classe iteráveis. Este método deve devolver um iterador. Um iterador é um objeto com um método next(). É possível implementar ambos __iter__() e next() na mesma classe, e ter __iter__() de volta self. Isto vai funcionar para casos simples, mas não quando você quer dois iteradores looping sobre o mesmo objeto ao mesmo tempo.

Então esse é o protocolo iterator, muitos objetos aplicar o presente Protocolo:
  1. listas incorporadas, dicionários, tuplas, conjuntos, ficheiros.
  2. classes definidas pelo utilizador que implementam __iter__().
  3. Geradores.

Note que um laço for não sabe com que tipo de objecto está a lidar - apenas segue o protocolo iterator, e fica feliz por obter item após item como chama next(). As listas incorporadas devolvem os seus itens um a um, os dicionários devolvem as chaves um a um, os ficheiros devolvem a linhas uma a uma, etc. E os geradores regressam... bem, é aí que yield entra:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Em Vez de yield declarações, se você tivesse três return declarações em f123() apenas o primeiro seria executado, e a função de saída. Mas f123() não é uma função comum. Quando f123() é chamado, ele não devolve nenhum dos valores nas declarações de rendimento! Devolve um objecto gerador. Além disso, a função realmente não sai - ela vai para um estado suspenso. Quando a for o ciclo tenta fazer um ciclo sobre o objecto gerador, a função retoma do seu estado suspenso na linha seguinte após o {[[2]} de onde voltou anteriormente, executa a próxima linha de código, neste caso uma instrução yield, e devolve-a como o próximo item. Isto acontece até que a função saia, em que ponto o gerador levanta StopIteration, e o laço sai.

Então, o objecto gerador é como um adaptador, numa extremidade exibe o protocolo iterador, expondo __iter__() e next() métodos para manter o ciclo feliz. No outro extremo, no entanto, ele executa a função apenas o suficiente para obter o próximo valor fora dele, e coloca-o de volta no modo suspenso. Para Quê Usar Geradores?

Normalmente você pode escrever um código que não usa geradores, mas implementa a mesma lógica. Uma opção é usar a lista temporária 'trick' que mencionei antes. Isso não vai funcionar em todos os casos, por exemplo, se você tem laços infinitos, ou pode fazer uso ineficiente da memória quando tens uma lista muito longa. A outra abordagem é implementar uma nova classe iterável SomethingIter que mantém os membros do estado em instância e executa o próximo passo lógico no seu método next() (ou __next__() em Python 3). Dependendo da lógica, o código dentro do método next() pode acabar parecendo muito complexo e ser propenso a bugs. Aqui os geradores fornecem uma solução limpa e fácil.

 1677
Author: user28409, 2016-08-15 21:29:29
Pensa assim:

Um iterador é apenas um termo de sonoridade extravagante para um objeto que tem um próximo() método. Então uma função yield-ed acaba sendo algo assim:

Versão Original:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

Isto é basicamente o que o interpretador Python faz com o código acima:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i
Para mais informações sobre o que está a acontecer nos bastidores, o loop pode ser reescrito para isto:
iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass
Isso faz mais sentido ou só te confunde mais? :)

Devo notar que este é uma simplificação excessiva para fins ilustrativos. :)

 411
Author: Jason Baker, 2018-05-20 10:02:17

A palavra-chave é reduzida a dois factos simples:

  1. Se o compilador detectar a palavra-chave yield em qualquer lugar dentro de uma função, essa função já não retorna através da instrução return. em vez disso, IT immediately returns a lazy "pending list" object called a generator
  2. Um gerador é iterável. O que é um iterável? É qualquer coisa como um list ou set ou {[12] } ou dict-view, com um protocolo incorporado para visitar cada elemento numa determinada ordem .

Em poucas palavras: um gerador é uma lista preguiçosa, incrementalmente pendente, e yield as instruções permitem-lhe usar a notação da função para programar os valores da lista o gerador deve cuspir gradualmente.

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

Exemplo

Vamos definir uma função como a do Python. Chamando makeRange(n) devolve um gerador:
def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

Para forçar o gerador para devolver imediatamente os seus valores pendentes, você pode passá-lo em list() (assim como você poderia qualquer iterável):

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

Comparando exemplo com "apenas retornando uma lista"

O exemplo acima pode ser pensado apenas como criando uma lista à qual você adiciona e retorna:

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]
No entanto, há uma grande diferença; ver a última secção.

Como utilizar geradores

Um iterável é a última parte de uma compreensão da lista, e todos os geradores são iteráveis, por isso são frequentemente usados assim.

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

Para obter uma melhor sensação para os geradores, você pode brincar com o módulo itertools (certifique-se de usar chain.from_iterable em vez de chain quando necessário). Por exemplo, você pode até usar geradores para implementar listas preguiçosas infinitamente longas como itertools.count(). Você pode implementar seu próprio def enumerate(iterable): zip(count(), iterable), ou alternativamente fazê-lo com a palavra-chave yield em um ciclo de tempo.

Por favor, a ter em conta: os geradores podem ser utilizados para muitas mais coisas., tais comoimplementar coroutinas ou programação não-determinística ou outras coisas elegantes. No entanto, o ponto de vista" listas preguiçosas " que eu apresento aqui é o uso mais comum que você vai encontrar.


Nos bastidores É assim que funciona o" protocolo de iteração Python". Isto é, o que está acontecendo quando você faz list(makeRange(5)). Isto é o que eu descrevi anteriormente como uma "lista preguiçosa e incremental".
>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

A função embutida next() apenas chama os objectos .next() função, que é uma parte do" protocolo de iteração " e é encontrado em todos os iteradores. Você pode usar manualmente a função next() (e outras partes do protocolo de iteração) para implementar coisas extravagantes, geralmente à custa da legibilidade, então tente evitar fazer isso...


Minutiae

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

Em Python-speak, um iterable é qualquer objecto que " compreenda o conceito de um for-loop " como uma lista [1,2,3], e um iterator é uma instância específica do for-loop solicitado como [1,2,3].__iter__(). Um gerador é exatamente o mesmo que qualquer iterador, exceto pela forma como foi escrito (com sintaxe de função).

Quando se pede um iterador de uma lista, cria-se um iterador novo. No entanto, quando você pede um iterator de um iterator (o que você raramente faria), ele apenas lhe dá uma cópia de si mesmo.

Assim, no caso improvável que estás a falhar em fazer algo assim...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... então lembre-se que um gerador é um iterador ; isto é, é um uso único. Se você quiser reutilizá-lo, você deve ligar para myRange(...) novamente. Se você precisar usar o resultado duas vezes, converter o resultado para uma lista e armazená-lo em uma variável x = list(myRange(5)). Aqueles que absolutamente precisam clonar um gerador (por exemplo, que estão fazendo terrivelmente hackish metaprogramming) podem usar itertools.tee se absolutamente necessário, uma vez que o a proposta de normas iterator Python copiável foi adiada.

 354
Author: ninjagecko, 2017-03-19 08:07:35

O Que Faz A Palavra-chave yield em Python?

Resumo Das Respostas

  • Uma função com yield, quando chamado, retorna uma Gerador.
  • Os geradores são iteradores porque implementam o protocolo iterador, para que possas entreter-te com eles.
  • um gerador também pode ser enviado informação , tornando-se conceptualmente um coroutine .
  • Em Python 3, você pode delegar de um gerador para outro em ambas as direções com yield from.
  • (Appendix critiques a couple of answers, including the top one, and discusses the use of return in a generator.)

Geradores:

yield é apenas legal dentro de uma definição de função, e a inclusão de yield em uma definição de função faz com que retorne um gerador.

A a ideia para geradores vem de outras línguas (Ver nota 1) com diferentes implementações. Nos geradores de Python, a execução do código é congelada no ponto do rendimento. Quando o gerador é chamado (métodos são discutidos abaixo) a execução recomeça e então congela no próximo rendimento.

yield fornece um modo fácil de implementação do protocolo iterator ([90]}, definido pelos dois métodos seguintes:: __iter__ e next (Python 2) ou __next__ (Python 3). Quer desses métodos faça de um objecto um iterador que possa digitar-verificar com a base abstracta Iterator Classe do módulo collections

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

O tipo gerador é um subtipo de iterador:

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

E se necessário, podemos digitar assim:

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

Uma característica de um Iterator é que uma vez esgotado {[[90]}, você não pode reutilizá-lo ou reiniciá-lo:

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

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

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

Pode-se produzir dados programaticamente, por exemplo:

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

O gerador simples acima também é equivalente ao Python 3.3 (e não está disponível no Python 2), você pode usar yield from:

def func(an_iterable):
    yield from an_iterable

No entanto, yield from também permite a delegação em subgéneros., que será explicado na secção seguinte sobre delegação cooperativa com sub-coroutinas.

Coroutinas:

yield forma uma expressão que permite dados a enviar para o gerador (ver Nota 3)

Aqui está um exemplo, tome nota da variável received, que apontará para os dados que são enviados para o gerador:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)
Primeiro, temos de colocar o gerador em fila com a função builtin., next. Vai. chame o método apropriado next ou __next__, dependendo da versão de Python que está a usar:
>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0
E agora podemos enviar dados para o gerador. ([162]}enviar None é o mesmo que chamar next.) :
>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5
([165]) delegação cooperativa na sub-Coroutine com yield from Agora, lembre-se que yield from está disponível no Python 3. Isto permite-nos delegar coroutinas a uma subcoroutina:
def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()
E agora podemos delegar a funcionalidade a um sub-gerador e pode ser usado. por um gerador igual ao acima:
>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

Você pode ler mais sobre a semântica precisa de yield fromem PEP 380.

Outros métodos: atira.

O método close aumenta GeneratorExit no ponto em que a função a execução foi congelada. Isto também será chamado por __del__ assim você pode colocar qualquer código de limpeza onde você lidar com o GeneratorExit:

>>> my_account.close()
Também pode abrir uma excepção que pode ser tratada no gerador. ou propagado de volta ao utilizador:
>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Conclusão

Acho que já abordei todos os aspectos da seguinte pergunta:

O que faz a palavra-chave yield em Python?

Acontece que yield faz muito. Tenho a certeza que posso acrescentar ainda mais exemplos completos disso. Se quiser mais ou tiver alguma crítica construtiva, avise-me comentando abaixo.

Apêndice:

Crítica da resposta superior / aceite * *

  • está confuso sobre o que torna um iterável , apenas usando uma lista como exemplo. Veja minhas referências acima, mas em resumo: um iterável tem um método __iter__ retornando um Iterator Um iterator fornece um método .next (Python 2 ou .__next__ (Python 3), que é implicitamente chamado por for loops até que ele levante StopIteration, e uma vez que ele o faça, ele continuará a fazê-lo.
  • Então usa uma expressão do gerador para descrever o que é um gerador. Uma vez que um gerador é simplesmente uma maneira conveniente de criar um iterador , ele só confunde a matéria, e ainda não chegamos à parteyield.
  • em controlo a exaustão do gerador {[80] } ele chama o método .next, quando em vez disso ele deve usar a função de Construção, next. Seria uma camada apropriada de indireção, porque seu código não funciona no Python 3.
  • Itertools? Isto não era relevante para o que yield faz.
  • nenhuma discussão dos métodos que yield fornece juntamente com a nova funcionalidade yield from no Python 3. a resposta superior / aceite é uma resposta muito incompleta.
Crítica da resposta sugerindo uma expressão ou compreensão geradora.

A gramática permite actualmente qualquer expressão na compreensão de uma lista.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

Uma vez que o rendimento é uma expressão, tem sido considerado por alguns como interessante usá - lo em compreensões ou expressão geradora-apesar de não citar nenhum caso de uso particularmente bom.

Os programadores do CPython estão a discutir depreciar o seu subsídio. Aqui está um post relevante do correio lista:

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

On Sun, 29 Jan 2017 at 16: 39 Craig Rodrigues wrote:

Não me importo com nenhuma das abordagens. Deixando as coisas como estão no Python 3 não é bom, IMHO.
O meu voto é que seja um erro Sintaxerror, já que não está a conseguir o que espera de mim. sintaxe.
Concordo que é um lugar sensato para acabarmos. codigo confiar no comportamento actual é realmente demasiado inteligente para ser passivel. Em termos de chegar lá, é provável que queiramos:
    SyntaxWarning ou Depreciationwarning em 3.7 Aviso Py3k em 2.7.x SyntaxError em 3, 8
Saúde, Nick.

-- Nick Coghlan / ncoghlan at gmail.com / Brisbane, Austrália

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

Conclusão, até que os programadores do CPython nos digam o contrário: Não coloque yield numa expressão ou compreensão geradora.

A declaração return num gerador

Em Python 2:

Numa função geradora, a declaração return não é permitida. incluir um expression_list. Nesse contexto, um bare return indica que o gerador está feito e fará com que StopIteration seja levantado.

An expression_list é basicamente qualquer número de expressões separadas por vírgulas - essencialmente, no Python 2, você pode parar o gerador com {[[18]}, mas você não pode devolver um valor.

Em Python 3:

Numa função do gerador, a declaração return indica que o gerador está feito e fará com que StopIteration seja elevado. O o valor devolvido (se existir) é usado como argumento para construir StopIteration e torna-se o atributo StopIteration.value.

Notas de rodapé

  1. as línguas CLU, Sather e Icon foram referenciadas na proposta para introduzir o conceito de geradores para Python. A ideia geral é: que uma função pode manter o estado interno e produzir intermediário pontos de dados a pedido do utilizador. Isto prometeu ser superior no desempenho. outras abordagens, incluindo Python threading , que nem sequer está disponível em alguns sistemas.

  2. isto 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, o que significa que só pode aparecer no início de uma linha num bloco de código. Agora yield cria um rendimento expressao. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Esta alteração foi proposta para permitir que um utilizador enviasse dados para o gerador tal como talvez alguém a Receba. Para enviar dados, deve-se ser capaz de atribuí-lo a algo, e para isso, uma declaração não vai resultar.

 263
Author: Aaron Hall, 2017-12-06 20:20:40

yield é como return - devolve o que lhe disser (como um gerador). A diferença é que da próxima vez que você ligar para o gerador, a execução começa da última chamada para a declaração yield. Ao contrário do return, a estrutura da pilha não é limpa quando ocorre um rendimento, no entanto o controle é transferido de volta para o chamador, de modo que seu estado irá retomar a próxima vez que a função.

No caso do seu código, a função {[3] } está a agir como um iterador para que quando você Extenda sua lista, adiciona um elemento de cada vez para a nova lista.

list.extend chama um iterador até estar exausto. No caso da amostra de código que você postou, seria muito mais claro apenas retornar uma tupla e adicioná-la à lista.

 235
Author: Douglas Mayle, 2018-05-20 09:57:33
Há mais uma coisa a mencionar: uma função que rende não tem de terminar. Escrevi um código como este:
def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Então posso usá-lo noutro código como este:

for f in fib():
    if some_condition: break
    coolfuncs(f);
Realmente ajuda a simplificar alguns problemas, e torna algumas coisas mais fáceis de trabalhar.
 186
Author: Claudiu, 2013-04-21 15:42:14

Para aqueles que preferem um exemplo de trabalho mínimo, meditem nesta sessão interactiva em Python :

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed
 156
Author: Daniel, 2013-01-25 19:19:12
O rendimento dá-te 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 podem ver, no primeiro caso, o foo guarda a lista inteira em memória de uma só vez. Não é grande coisa para uma lista com 5 elementos, mas e se você quiser uma lista de 5 milhões? Não só isso é um enorme Devorador de memória, também custa muito tempo para construir no momento em que a função é chamada. No segundo caso, o bar dá-te um gerador. Um gerador é iterável--o que significa que você pode usá-lo em um laço for, etc, mas cada valor só pode ser acedido uma vez. Todos os valores também não são armazenados na memória ao mesmo tempo; o objeto gerador "lembra" onde estava no looping da última vez que você chamou-desta forma, se você está usando um iterável para (digamos) contar até 50 bilhões, você não tem que contar até 50 bilhões de uma vez e armazenar os 50 bilhões de números para contar. Mais uma vez, este é um exemplo bastante inventado, você provavelmente usaria itertools se você realmente quisesse contar até 50 bilhões. :) Isto é o mais simples. caso de utilização de geradores. Como você disse, ele pode ser usado para escrever permutações eficientes, usando yield para empurrar as coisas através da pilha de chamadas em vez de usar algum tipo de variável de pilha. Geradores também podem ser usados para árvores especializadas transversais, e todos os tipos de outras coisas.
 135
Author: RBansal, 2013-01-16 06:42:09
Está a devolver um gerador. Eu não estou particularmente familiarizado com Python, mas eu acredito que é o mesmo tipo de coisa que os blocos iterator de C# Se você está familiarizado com esses.

Existe um artigo da IBM {[3] } que o explica razoavelmente bem (para Python), tanto quanto consigo ver.

A ideia chave é que o compilador/intérprete / o que quer que faça algum truque para que, no que diz respeito ao chamador, eles possam continuar a ligar a seguir () e ele vai continuar a devolver os valores - como se o método do gerador estivesse em pausa. Agora obviamente você não pode realmente "pausar" um método, então o compilador constrói uma máquina de Estado para que você se lembre onde você está atualmente e como as variáveis locais etc parecem. Isto é muito mais fácil do que escrever um iterador.

 127
Author: Jon Skeet, 2008-10-23 22:26:06

TL; DR

Em vez disto:

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

Faz isto:

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

Sempre que você se encontra construindo uma lista do zero, yield Cada peça em vez.

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

yield é uma maneira doce de dizer

Construir uma série de coisas

O mesmo comportamento:

>>> for square in squares_list(4):
...     print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
...     print(square)
...
0
1
4
9

Comportamento diferente:

O rendimento é de passagem única : só se pode iterar através quando. Quando uma função tem um rendimento nela, chamamos-lhe função gerador . E um iterador é o que volta. Isso é Revelador. Perdemos a conveniência de um recipiente, mas ganhamos o poder de uma série arbitrariamente longa.

O rendimento é preguiçoso , atrasa a computação. Uma função com um rendimento nela realmente não executa nada quando você a chama. O objecto iterador que devolve usa magia para manter o contexto interno da função. Cada vez que você chama next() no iterador (isso acontece em um for-loop) execução polegadas para frente para o próximo rendimento. (return aumenta StopIteration e termina a série.)

O rendimento é versátil . Pode fazer laços infinitos:

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

Se precisar de múltiplos passes e a série não for muito longa, basta ligar list() para ele:

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

Escolha brilhante da palavra yield porque ambos os significados aplicam-se:

Yield - produzir ou fornecer (como na agricultura)

...fornecer os dados seguintes da série.

Yield - ceder ou renunciar (como no poder político)

...renuncia à execução da CPU até o iterator avançar.

 126
Author: Bob Stein, 2018-09-07 17:15:54
Há um tipo de resposta que ainda não me parece ter sido dada, entre as muitas grandes respostas que descrevem como usar geradores. Aqui está a resposta da teoria da linguagem de programação:

A instrução yield em Python devolve um gerador. Um gerador em Python é uma função que retorna continuações (e, especificamente, um tipo de co-rotina, mas continuações representam o mais mecanismo geral para entender o que está acontecendo).

Continuações em a teoria das linguagens de programação é um tipo muito mais fundamental de computação, mas elas não são frequentemente usadas, porque elas são extremamente difíceis de raciocinar e também muito difíceis de implementar. Mas a idéia do que é uma continuação, é simples: é o estado de uma computação que ainda não terminou. Neste estado, os valores atuais das variáveis, as operações que ainda não foram realizadas, e assim por diante, são salvos. Então, em algum momento mais tarde no programa a continuação pode ser invocados, de modo que as variáveis do programa são repostas a esse estado e as operações que foram salvas são realizadas.

As continuações, desta forma mais geral, podem ser implementadas de duas maneiras. Da maneira call/cc, a pilha do programa é literalmente guardada e então, quando a continuação é invocada, a pilha é restaurada.

No estilo de passagem contínua (CPS), as continuações são apenas funções normais (apenas em linguagens onde as funções são de primeira classe) que o programador explicitamente gerencia e passa para sub-rotinas. Neste estilo, o estado do programa é representado por encerramentos (e as variáveis que por acaso são codificadas neles) ao invés de variáveis que residem em algum lugar na pilha. Funções que gerenciam o fluxo de controle aceitam continuação como argumentos (em algumas variações de CPS, funções podem aceitar múltiplas continuações) e manipulam o fluxo de controle invocando-os simplesmente chamando-os e retornando depois. Um exemplo muito simples de continuação o estilo de passagem é o seguinte:

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

Neste (muito simplista) exemplo, o programador salva a operação de realmente gravar o arquivo em uma continuação (que potencialmente pode ser uma operação muito complexa, com muitos detalhes para escrever) e, em seguida, passa-continuação (que eu.e, como um de primeira classe encerramento) para outro operador que faz um pouco mais de processamento, e, em seguida, chama-a, se necessário. (Eu uso este padrão de design muito na programação GUI real, ou porque ele me poupa linhas de código ou, mais importante, para gerenciar o fluxo de controle após o desencadeamento de eventos GUI.)

O resto deste post irá, sem perda de generalidade, conceituar continuações como CPS, porque é muito mais fácil de entender e ler.


Agora vamos falar de geradores em Python. Os geradores são um subtipo específico de continuação. Considerando que as continuações são capazes, em geral, de salvar o estado de um cálculo (ou seja, a chamada do programa stack), Os geradores só são capazes de salvar o estado de iteração sobre um iterador . Embora esta definição seja ligeiramente enganosa para certos casos de utilização 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 (isto é, for x in collection: do_something(x)). Presente exemplo ilustra a potência dos geradores: se alguma coisa é um iterador, um gerador pode salvar o estado de sua iteração.

Para reiterar: continuações podem salvar o estado da pilha de um programa e geradores podem salvar o estado de iteração. Isso significa que as continuações são muito mais poderosas do que os geradores, mas também que os geradores são muito, muito mais fácil. Eles são mais fáceis para o designer de linguagem para implementar, e eles são mais fáceis para o programador para usar (se você tem algum tempo para queime, tente ler e entender esta página sobre continuações e ligue / cc ).

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

Sempre que yield é chamado, ele diz à função para retornar uma continuação. Quando a função é chamada novamente, ela começa de onde quer que ela parou. Assim, Em pseudo-pseudocode (isto é, não pseudocode, mas não código) o método do gerador next é basicamente como

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

Onde a palavra-chave {[[4]} é na verdade açúcar sintático para a função gerador real, basicamente algo como:

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

Lembre-se que isto é apenas pseudocode e a implementação real de geradores em Python é mais complexa. Mas como um exercício para entender o que está acontecendo, tente usar o estilo de passagem de continuação para implementar objetos geradores sem o uso da palavra-chave yield.

 123
Author: aestrivex, 2018-05-20 10:25:32
Aqui está um exemplo em linguagem simples. Eu vou fornecer uma correspondência entre conceitos humanos de alto nível para conceitos de baixo nível Python. Quero operar numa sequência de números, mas não quero incomodar-me com a criação dessa sequência, quero concentrar-me apenas na operação que quero fazer. Então, eu faço o seguinte:
    Eu ligo-te e digo-te que quero uma sequência de números que seja produzida de uma forma específica, e depois digo-te o que é que o é o algoritmo.
    Este passo corresponde a def ining a função gerador, ou seja, a função que contém um yield.
  • algum dia depois, eu digo-te,"OK, prepara-te para me dizeres A sequência de números".
    Este passo corresponde a chamar a função gerador que retorna um objeto gerador.Note que ainda não me diz números, pegue no seu papel e lápis.
  • Eu pergunto-te, "diz-me o próximo número", e tu dizes-me o primeiro número.; depois disso, espera que te Peça o próximo número. O teu trabalho é lembrar-te Onde estavas, quais os números que já disseste, e qual é o próximo número. Não quero saber dos detalhes.
    Este passo corresponde à chamada .next() no objecto gerador.
  • ... repetir o passo anterior, até...
  • Eventualmente, podes chegar ao fim. Não me dizes um número, só gritas: "aguenta os cavalos! Estou farto! Acabaram-se os números!"
    Este passo corresponde ao objecto gerador que termina a sua tarefa e levanta uma excepção StopIteration a função do gerador não precisa de levantar a excepção. É levantado automaticamente quando a função termina ou emite um return.

Isto é o que um gerador faz (uma função que contém um yield); ele começa a executar, pára sempre que faz um yield, e quando lhe é pedido um valor .next() continua a partir do ponto em que foi o último. Ele se encaixa perfeitamente pelo design com o protocolo iterator de Python, que descreve como solicitar sequencialmente valores.

O utilizador mais famoso do protocolo iterator é o comando for em Python. Então, sempre que você faz um:

for item in sequence:

Não importa se sequence é uma lista, uma cadeia de caracteres, um dicionário ou um gerador {[[47]} como descrito acima; o resultado é o mesmo: você lê itens de uma sequência um por um.

Note que def ining a function which contains a yield keyword is not the only way to create a generator; it is just a maneira mais fácil de criar um.

Para informações mais precisas, leia sobretipos iteradores , a declaração de rendimentoe geradoresna documentação em Python.

 109
Author: tzot, 2018-05-20 10:06:05

Apesar de muitas respostas mostrarem por que você usaria um yield para criar um gerador, existem mais usos para yield. É muito fácil fazer um coroutine, que permite a passagem de informações entre dois blocos de código. Não vou repetir nenhum dos bons exemplos que já foram dados sobre o uso de yield para criar um gerador.

Para ajudar a compreender o que um yield faz no seguinte código, pode usar o seu dedo para localizar o ciclo através de qualquer código que tenha UM yield. Sempre seu dedo bate no yield, você tem que esperar por um next ou um send para ser inserido. Quando um next é chamado, você rastreia através do Código até que você carrega no yield ... o código à direita do yield é avaliado e devolvido ao ouvinte ... então você espera. Quando next é chamado novamente, você executa outro loop através do Código. No entanto, você vai notar que em um coroutine, yield também pode ser usado com um send ... que irá enviar um valor do chamador para a função de rendimento. Se for dado, então yield recebe o valor enviado, e cospe-o para fora do lado esquerdo ... em seguida, o traço através do Código progride até você atingir o yield novamente (retornando o valor no final, como se next fosse chamado).

Por exemplo:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
 97
Author: Mike McKerns, 2014-02-04 02:27:35

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

yield from <expr>

De PEP 380 -- sintaxe para delegar a um Subgenerador:

É proposta uma sintaxe para um gerador delegar parte das suas operações a outro gerador. Isto permite que uma seção de código contendo "rendimento" seja contabilizada e colocada em outro gerador. Além disso, o subgenerador é permitido retornar com um valor, e o valor é disponibilizado para a delegação gerador.

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

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

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

Para evitar que as coroutinas sejam confundidas com um gerador regular (hoje yield é usado em ambos).

 86
Author: Sławomir Lenart, 2018-05-20 10:34:03

Eu ia postar "leia a Página 19 da' Python: Essential Reference 'de Beazley para uma rápida Descrição dos geradores", mas muitos outros já postaram boas descrições.

Além disso, note que yield pode ser usado em coroutinas como o dual da sua utilização em funções geradoras. Embora não seja o mesmo uso que o seu excerto de código, (yield) pode ser usado como uma expressão numa função. Quando um chamador envia um valor para o método usando o método send(), então o coroutine irá executar até que seja encontrada a próxima declaração (yield).

Os geradores e as coroutinas são uma forma fixe de configurar aplicações do tipo fluxo de dados. Pensei que valeria a pena saber sobre o outro uso da declaração yield em funções.

 77
Author: johnzachary, 2013-01-28 01:37:10

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

Como gerador de Python:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

Utilização de dispositivos lexicais em vez de geradores

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

Usar o encerramento de objectos em vez de geradores (porque Os fechos e os objectos são equivalentes)

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
 77
Author: Dustin Getz, 2017-10-24 10:46:05

Do ponto de vista da programação, os iteradores são implementados como blocos.

Para implementar iteradores, geradores e grupos de linhas para execução simultânea, etc. como troncos (também chamados funções anônimas), uma pessoa usa mensagens enviadas para um objeto de fechamento, que tem um despachante, e o despachante responde para "mensagens".

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

"a seguir" é uma mensagem enviada para um encerramento, criada pelo "iter " call.

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

Aqui está uma demonstração que usa a estrutura dos R6RS, mas a semântica é absolutamente idêntica à do Python. é o mesmo modelo de computação, e só é necessária uma mudança de sintaxe para reescrevê-la em Python.

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->
 70
Author: alinsoar, 2018-05-20 10:29:34
Todas as grandes respostas, mas um pouco difíceis para os novatos. Presumo que tenhas aprendido a declaração.

Como analogia, return e yield são gémeos. return significa "retorno e paragem" enquanto que "Rendimento" significa "Retorno, MAS continuar"

  1. tenta obter uma lista numérica com return.
def num_list(n):
    for i in range(n):
        return i

Passa.

In [5]: num_list(3)
Out[5]: 0
Veja, você recebe apenas um número em vez de uma lista deles. Nunca permite que prevaleças feliz, apenas ... implementa uma vez e desiste.
  1. Aí vem yield

Substituir return por yield:

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]
Agora, ganhas para obter todos os números.

Comparando com return que corre uma vez e para, yield executa vezes que planeaste. Você pode interpretar return como return one of them, e yield como return all of them. Isto chama-se iterable.

  1. mais um passo podemos reescrever yield a declaração 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 cerne da questão. yield.

A diferença entre uma lista return saídas e o objecto yield saída é:

Você irá sempre obter [0, 1, 2] de um objecto da lista, mas só os poderá recuperar de 'a saída do objecto yield' uma vez. Então, ele tem um novo nome generator objeto como mostrado em Out[11]: <generator object num_list at 0x10327c990>.

Para concluir, como metáfora para grok it:
  • return e {[6] } são gémeos
  • list e {[26] } são gémeos
 66
Author: JawSaw, 2018-05-28 09:06:22

Aqui está um exemplo simples:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

Resultado:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

Eu não sou um desenvolvedor Python, mas parece-me yield mantém a posição do fluxo do programa e o próximo loop começa a partir da posição "yield". Parece que está à espera nessa posição, e pouco antes disso, devolvendo um valor lá fora, e da próxima vez continua a funcionar.

Parece ser uma habilidade interessante e agradável.
 63
Author: Engin OZTURK, 2018-05-20 10:31:01
Aqui está uma imagem mental do que faz.

Gosto de pensar num tópico como tendo uma pilha (mesmo quando não é implementada dessa forma).

Quando uma função normal é chamada, ela coloca as suas variáveis locais na pilha, faz alguma computação, então limpa a pilha e retorna. Os valores de suas variáveis locais nunca mais são vistos.

Com uma função yield, quando o seu código começa a correr (isto é, após a função ser chamada, devolvendo um objecto gerador, cujo next() o método é então invocado), ele também coloca suas variáveis locais na pilha e calcula por um tempo. Mas então, quando ele atinge a declaração yield, antes de limpar a sua parte da pilha e retornar, ele tira uma foto de suas variáveis locais e as armazena no objeto gerador. Ele também escreve o lugar onde ele está atualmente até em seu código (ou seja, a declaração particular yield).

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

Quando next() é chamado posteriormente, ele recupera os pertences da função na pilha e re-anima-o. A função continua a calcular a partir de onde parou, alheio ao fato de que tinha acabado de passar uma eternidade em armazenamento frio.

Compare os seguintes exemplos:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12
Quando chamamos a segunda função, ela se comporta de forma muito diferente da primeira. A declaração yield pode ser inalcançável, mas se estiver presente em qualquer lugar, muda a natureza da com o que estamos a lidar.
>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>
Chamar não funciona o código, mas faz um gerador com o código. (Talvez seja uma boa idéia nomear essas coisas com o prefixo yielder para legibilidade.)
>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

Os campos gi_code e gi_frame são onde o estado congelado está armazenado. Explorando-os com dir(..), podemos confirmar que o nosso modelo mental acima é credível.

 54
Author: Evgeni Sergeev, 2017-03-01 13:36:58
Como todas as respostas sugerem, yield é usado para criar um gerador de sequência. É usado para gerar uma sequência dinâmica. Por exemplo, ao ler um arquivo linha a linha em uma rede, você pode usar a função yield da seguinte forma:
def getNextLines():
   while con.isOpen():
       yield con.read()

Pode usá-lo no seu código da seguinte forma:

for line in getNextLines():
    doSomeThing(line)

controlo de execução transferência gotcha

O controlo de execução será transferido de getNextLines() para o laço for quando o rendimento for executado. Assim, cada vez que getNextLines() é invocado, a execução começa a partir do ponto onde foi parado da última vez.

Assim, em resumo, uma função com o seguinte código

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

Irá imprimir

"first time"
"second time"
"third time"
"Now some useful value 12"
 42
Author: Mangu Singh Rajpurohit, 2018-05-20 10:42:59

Yield é um objecto

Um return numa função irá devolver um único valor.

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

Mais importante ainda, yieldé uma barreira .
Como barreira na língua CUDA, não transferirá o controlo até chegar elaborar.
Isto é, irá executar o código na sua função desde o início até atingir yield. Então, devolverá o primeiro valor. do loop.

Então, cada outra chamada irá executar o loop que você escreveu na função mais uma vez, retornando o próximo valor até que não haja nenhum valor para retornar.

 38
Author: Kaleem Ullah, 2018-05-20 10:45:50
Em resumo, a declaração yield transforma a sua função numa fábrica que produz um objecto especial chamado a {6]} que envolve o corpo da sua função original. Quando o generator é iterado, executa a sua função até atingir o próximo yield, suspende então a execução e avalia o valor passado para yield. Ele repete este processo em cada iteração até que o caminho de execução saia da função. Por exemplo,
def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

Simplesmente Saídas

one
two
three

A energia vem de usar o gerador com um loop que calcula uma sequência, o gerador executa o loop parando cada vez para 'yield' o resultado seguinte do cálculo, desta forma calcula uma lista na altura, sendo o benefício a memória guardada para cálculos especialmente grandes

Diz que querias criar uma função própria range que produzisse uma gama iterável de números, podias fazê-lo assim,

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

E usa-o como isto.

for i in myRangeNaive(10):
    print i

Mas isto é ineficiente porque

  • você cria uma matriz que só usa uma vez (isto desperdiça memória)
  • Este código passa duas vezes por aquela matriz! :(
Felizmente, o Guido e a sua equipa foram generosos o suficiente para desenvolver geradores para podermos fazer isto.
def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i
Agora, em cada iteração, uma função no gerador chamada next() executa a função até atingir uma declaração de 'rendimento' em que pára e "produz" o valor ou atinge o fim da função. Neste caso, na primeira chamada, next() executa até à instrução yield e yield 'n', na próxima chamada irá executar a instrução incremental, saltar de volta para o 'while', avaliá-lo, e se for verdadeiro, irá parar e produzir' n ' novamente, irá continuar dessa forma até que a condição while retorne falso e o gerador salta para o fim da função.
 37
Author: redbandit, 2018-05-20 11:04:36

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

 34
Author: An Epic Person, 2015-05-20 06:19:32

(a minha resposta abaixo só fala da perspectiva de usar o gerador Python, não a implementação subjacente do mecanismo gerador , que envolve alguns truques de manipulação de pilha e pilha.)

Quando yield é usado em vez de um return numa função python, essa função é transformada em algo especial chamado generator function. Essa função irá retornar um objeto de generator Tipo. a palavra-chave yield é uma bandeira para notificar o compilador python para tratar tal função especialmente. as funções normais terminarão assim que algum valor for devolvido. Mas com a ajuda do compilador, a função do gerador pode ser considerada como refutável. Ou seja, o contexto de execução será restaurado e a execução continuará a partir da última execução. Até que você explicitamente chame return, o que irá levantar uma exceção StopIteration (que também faz parte do protocolo iterator), ou chegar ao fim da função. Encontrei muitas referências sobre generator mas este um do functional programming perspective é o mais digerível.

(Agora eu quero falar sobre a lógica por trás generator, e o iterator baseado no meu próprio entendimento. Espero que isto o ajude a compreender a motivação essencial de iterador e gerador. Tal conceito aparece em outras línguas, bem como C#.)

Pelo que sei, quando queremos processar um monte de dados, normalmente armazenamos os dados em algum lugar e depois processamo-los um a um. Mas isto ... intuitivo a abordagem é problemática. Se o volume de dados é enorme, é caro armazená-los como um todo de antemão. Então, em vez de armazenar o próprio data diretamente, por que não armazenar algum tipo de metadata indiretamente, isto é,the logic how the data is computed.

Existem 2 abordagens para embrulhar esses metadados.

    A abordagem OO, nós embrulhamos os metadados. Este é o chamado iterator que implementa o protocolo iterador (isto é, os métodos __next__(), e __iter__()). Isto é ... também o padrão de design do iterador geralmente visto.
  1. a abordagem funcional, nós embrulhamos os metadados as a function. Isto é ... os chamados generator function. Mas sob o capô, o iterador devolvido generator object Porque também implementa o protocolo iterator.
De qualquer forma, um iterador é criado, ou seja, algum objeto que pode lhe dar os dados que você quer. A abordagem OO pode ser um pouco complexa. Seja como for, Quem deve usar é contigo.
 34
Author: smwikipedia, 2017-05-23 12:02:54

Muitas pessoas usam return em vez de yield, mas, em alguns casos, yield pode ser mais eficiente e mais fácil de trabalhar.

Aqui está um exemplo que yield é definitivamente melhor para:

Return (em função)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

Yield (em função)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

Funções de chamada

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

Ambas as funções fazem a mesma coisa, mas yield usa três linhas em vez de cinco e tem uma menos variável para se preocupar.

Este é o resultado do Código:

Output

Como podem ver, ambas as funções fazem a mesma coisa. A única diferença é que return_dates() dá uma lista e {[9] } dá um gerador.

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

 34
Author: Tom Fuller, 2018-05-20 11:02:52

A palavra-chave yield recolhe simplesmente os resultados. Pensa em:return +=

 33
Author: Bahtiyar Özdere, 2016-04-23 03:16:19
Aqui está uma abordagem simples baseada em yield, para calcular a série de fibonacci, explicada:
def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b
Quando introduzires isto no teu REPL e depois tentares chamá-lo, terás um resultado mistificante:
>>> fib()
<generator object fib at 0x7fa38394e3b8>

Isto é porque a presença de yield sinalizada para Python que você quer criar um gerador , isto é, um objeto que gera valores sob demanda.

Então, como se geram estes valores? Isso pode ser feito diretamente usando o built-in função next, ou, indirectamente, alimentando-a a uma construção que consome valores. Usando a função embutida next(), invoca directamente.next/__next__, forçando o gerador a produzir um valor:
>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5
Indiretamente, se você fornecer fib a um laço for, um inicializador list, um inicializador tuple, ou qualquer outra coisa que espere um objeto que gera / produz valores, você "consumirá" o gerador até que não mais valores possam ser produzidos por ele (e ele devolve):
results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib
Da mesma forma, com um inicializador tuple:
>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)
Um gerador difere de uma função no sentido de que é preguiçoso. Consegue-o mantendo o seu estado local e permitindo-lhe retomar sempre que precisar.

Quando invocaste pela primeira vez fibchamando-lhe:

f = fib()

O Python compila a função, encontra a palavra-chave yield e simplesmente devolve-lhe um objecto gerador. Não ajuda muito, ao que parece.

Quando em seguida, pedir gera o primeiro valor, direta ou indiretamente, executa todas as declarações que encontra, até encontrar um yield, então devolve o valor que você forneceu a yield e faz pausas. Para um exemplo que demonstre melhor isso, vamos usar algumas chamadas print (Substituir por print "text" se em Python 2):

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")
Agora, entra no REPL:
>>> gen = yielder("Hello, yield!")
Tem um objecto gerador à espera de um comando para gerar um valor. Use next e veja o que é impresso:
>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'
Os resultados não citados são o que está impresso. O resultado Citado é o que é devolvido de yield. Ligue para {[12] } novamente agora:
>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'
O gerador lembra-se que estava em pausa e recomeça a partir daí. A próxima mensagem é impressa e a busca pela declaração yield para pausar nela realizada novamente (devido ao laço while).
 29
Author: Jim Fasarakis Hilliard, 2017-07-12 12:44:24

Outro TL; DR

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

Gerador de iteradores: next() irá calcular o próximo elemento na altura (executar o código)

Você pode ver o rendimento / gerador como uma forma de executar manualmente o fluxo de controlo de fora( como continuar o ciclo um passo), chamando next, por mais complexo que seja o fluxo.

Nota : o gerador é Não {[9] } uma função normal. Lembra-se do estado anterior como variáveis locais (pilha). Para mais informações, ver outras respostas ou artigos. O gerador só pode ser iterado uma vez. Você poderia fazer sem yield, mas não seria tão agradável, assim que pode ser considerado' muito agradável ' língua açúcar.

 21
Author: Christophe Roussy, 2018-05-20 10:58:25