Pergunta

Qual é a utilidade do yield palavra-chave em 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?Uma lista é retornada?Um único elemento?É chamado de novo?Quando as chamadas subsequentes serão interrompidas?


1.Este trecho de código foi escrito por Jochen Schulz (jrschulz), que criou uma excelente biblioteca Python para espaços métricos.Este é o link para a fonte completa: Módulo mspace.

Foi útil?

Solução

Para entender o que yield faz, você deve entender o que geradores são.E antes que você possa entender os geradores, você deve entender iteráveis.

Iteráveis

Ao criar uma lista, você pode ler seus itens um por um.Ler seus itens um por um é chamado de iteração:

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

mylist é um iterável.Ao usar uma compreensão de lista, você cria uma lista e, portanto, um iterável:

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

Tudo que você pode usar "for... in..."on é 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ê deseja 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 instantaneamente:

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

É exatamente a mesma coisa, exceto que você usou () em vez de [].Mas você não pode executar 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.

Colheita

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

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

Aqui está um exemplo inútil, mas é útil quando você sabe que sua função retornará um conjunto enorme de valores que você só precisará ler uma vez.

Dominar yield, você deve entender isso quando você chama a função, o código escrito no corpo da função não é executado. A função retorna apenas o objeto gerador, isso é um pouco complicado :-)

Então, seu código continuará de onde parou todas as vezes for usa o gerador.

Agora a parte difícil:

A primeira vez que for chama o objeto gerador criado a partir da sua função, ele executará o código da sua função desde o início até atingir yield, então ele retornará o primeiro valor do loop.Então, cada outra chamada executará o loop que você escreveu na função mais uma vez e retornará o próximo valor, até que não haja mais valor para retornar.

O gerador é considerado vazio quando a função é executada, mas não atinge yield não mais.Pode ser porque o loop chegou ao fim ou porque você não satisfaz uma "if/else" não mais.


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, pois 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, pois não são aplicados no mesmo nó.

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

Normalmente 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 ganha 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 querer que todos 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 iteráveis ​​para funcionar com strings, listas, tuplas e geradores!Isso é chamado de digitação de pato e é uma das razões pelas quais o Python é tão legal.Mas isso é outra história, para outra pergunta...

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

Controlando a exaustão 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
...

Observação: 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.Você já desejou duplicar um gerador?Encadear dois geradores?Agrupar valores em uma lista aninhada com uma linha? Map / Zip sem criar outra lista?

Então é só import itertools.

Um exemplo?Vejamos 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)]

Compreendendo os mecanismos internos da iteração

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

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

Outras dicas

Atalho para entender yield

Quando você vê uma função com yield Declarações, aplique esse truque fácil de entender o que vai acontecer:

  1. Insira uma linha result = [] No início da função.
  2. Substitua cada um yield expr com result.append(expr).
  3. Insira uma linha return result na parte inferior da função.
  4. Yay - não mais yield declarações! Leia e descubra o código.
  5. Compare a função com a definição original.

Esse truque pode lhe dar uma idéia da lógica por trás da função, mas o que realmente acontece com yield é significativamente diferente que o que acontece na abordagem baseada na lista. Em muitos casos, a abordagem de rendimento também será muito mais eficiente e mais rápida na memória. Em outros casos, esse truque o deixará preso em um loop infinito, mesmo que a função original funcione bem. Continue lendo para saber mais ...

Não confunda seus iteráveis, iteradores e geradores

Primeiro, o Protocolo do iterador - Quando você escreve

for x in mylist:
    ...loop body...

Python executa as duas etapas a seguir:

  1. Recebe um iterador para mylist:

    Ligar iter(mylist) -> Isso retorna um objeto com um next() Método (ou __next__() em Python 3).

    Este é o passo que a maioria das pessoas esquece de falar sobre você

  2. Usa o iterador para dar um loop sobre os itens:

    Continue ligando para o next() método no iterador retornado da etapa 1. o valor de retorno de next() é atribuído a x e o corpo do loop é executado. Se uma exceção StopIteration é criado de dentro next(), isso significa que não há mais valores no iterador e o loop é excitado.

A verdade é que Python executa as duas etapas acima sempre que quiser Faça um loop o conteúdo de um objeto - para que possa ser um loop para otherlist.extend(mylist) (Onde otherlist é uma lista de Python).

Aqui mylist é um iterável Porque implementa o protocolo do iterador. Em uma classe definida pelo usuário, você pode implementar o __iter__() Método para tornar as instâncias da sua classe iterável. Este método deve retornar um iterador. Um iterador é um objeto com um next() método. É possível implementar ambos __iter__() e next() na mesma classe, e tenho __iter__() Retorna self. Isso funcionará para casos simples, mas não quando você deseja que dois iteradores sejam entregues sobre o mesmo objeto ao mesmo tempo.

Então esse é o protocolo Iterator, muitos objetos implementam este protocolo:

  1. Listas internas, dicionários, tuplas, conjuntos, arquivos.
  2. Classes definidas pelo usuário que implementam __iter__().
  3. Geradores.

Observe que a for O loop não sabe com que tipo de objeto está lidando - apenas segue o protocolo do iterador e fica feliz em obter item após item, conforme ele chama next(). Listas embutidas retornam seus itens um por um, os dicionários retornam o chaves um por um, os arquivos retornam o linhas um por um, etc. e os geradores retornam ... bem, é onde yield entra:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Ao invés de yield declarações, se você tivesse três return declarações em f123() Somente o primeiro seria executado e a função sairia. Mas f123() não é uma função comum. Quando f123() é chamado, isso não Retorne qualquer um dos valores nas declarações de rendimento! Ele retorna um objeto gerador. Além disso, a função realmente não sai - ela entra em um estado suspenso. Quando o for Loop tenta fazer loop sobre o objeto do gerador, a função retoma de seu estado suspenso na próxima linha após o yield Ele retornou anteriormente, executa a próxima linha de código, neste caso um yield declaração, e retorna isso como o próximo item. Isso acontece até que a função saia, momento em que o gerador aumenta StopIteration, e o loop sai.

Portanto, o objeto do gerador é como um adaptador - em uma extremidade, exibe o protocolo Iterator, expondo __iter__() e next() métodos para manter o for Loop feliz. No outro lado, no entanto, ele executa a função apenas o suficiente para obter o próximo valor e coloca -o de volta no modo suspenso.

Por que usar geradores?

Geralmente você pode escrever código que não usa geradores, mas implementa a mesma lógica. Uma opção é usar o 'truque' da lista temporária que mencionei antes. Isso não funcionará em todos os casos, por exemplo, se você tiver loops infinitos, ou pode fazer uso ineficiente da memória quando tiver uma lista muito longa. A outra abordagem é implementar uma nova classe iterável SomethingIter isso mantém o estado, por exemplo, membros e executa a próxima etapa lógica em item next() (ou __next__() no Python 3) Método. Dependendo da lógica, o código dentro do next() O método pode acabar parecendo muito complexo e propenso a bugs. Aqui os geradores fornecem uma solução limpa e fácil.

Pense desta maneira:

Um iterador é apenas um termo de som chique para um objeto que tem um next() método. Então, uma função de edição do rendimento acaba sendo algo assim:

Versão original:

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

for i in some_function():
    print i

Isso é basicamente o que o intérprete 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á acontecendo nos bastidores, o for O loop pode ser reescrito para isso:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Isso faz mais sentido ou apenas te confunde mais? :)

Eu deveria observar que isso é uma simplificação excessiva para fins ilustrativos. :)

O yield palavra-chave é reduzida a dois fatos simples:

  1. Se o compilador detectar o yield palavra-chave em qualquer lugar dentro de uma função, essa função não retorna mais através do return declaração. Em vez de, isto imediatamente retorna um objeto preguiçoso "lista pendente" chamado de gerador
  2. Um gerador é iterável.O que é um iterável?É qualquer coisa como um list ou set ou range ou dict-view, com um protocolo integrado para visitar cada elemento em uma determinada ordem.

Resumindo: um gerador é uma lista preguiçosa e pendente de forma incremental, e yield instruções permitem que você use notação de função para programar os valores da lista o gerador deve cuspir gradativamente.

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 isso é igual ao do Python range.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 a retornar imediatamente seus valores pendentes, você pode passá-lo para list() (assim como você faria com 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 uma simples criação de uma lista que você anexa 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]

Porém, há uma grande diferença;veja a última seção.


Como você pode usar geradores

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

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

Para ter uma ideia melhor dos geradores, você pode brincar com o itertools módulo (certifique-se de usar chain.from_iterable em vez de chain quando justificado).Por exemplo, você pode até usar geradores para implementar listas preguiçosas infinitamente longas, como itertools.count().Você poderia implementar seu próprio def enumerate(iterable): zip(count(), iterable), ou alternativamente fazê-lo com o yield palavra-chave em um loop while.

Observe:geradores podem realmente ser usados ​​para muito mais coisas, como implementando corrotinas 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)).Isso é o que descrevi anteriormente como uma "lista incremental e 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 integrada next() apenas chama os objetos .next() função, que faz parte do "protocolo de iteração" e é encontrada em todos os iteradores.Você pode usar manualmente o next() função (e outras partes do protocolo de iteração) para implementar coisas sofisticadas, geralmente às custas da legibilidade, então tente evitar fazer isso...


Minúcias

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

Na linguagem Python, um iterável é qualquer objeto que "entende o conceito de loop for" como uma lista [1,2,3], e um iterador é uma instância específica do loop for solicitado, como [1,2,3].__iter__().A gerador é exatamente igual a qualquer iterador, exceto pela forma 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 raramente faria), ele apenas fornece uma cópia de si mesmo.

Assim, no caso improvável de você não conseguir 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 é, é de uso único.Se você quiser reutilizá-lo, você deve ligar myRange(...) de novo.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 realmente precisam clonar um gerador (por exemplo, que estão fazendo uma metaprogramação terrivelmente hackeada) podem usar itertools.tee se for absolutamente necessário, já que o iterador copiável Python PEP proposta de padrões foi adiada.

O que faz o yield Palavra -chave em Python?

Resposta Esboço/Resumo

  • Uma função com yield, quando chamado, retorna a Gerador.
  • Geradores são iteradores porque implementam o Protocolo do iterador, para que você possa iterar sobre eles.
  • Um gerador também pode ser Informações enviadas, tornando -o conceitualmente um Coroutina.
  • Em 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 para geradores vem de outros idiomas (consulte a nota de rodapé 1) com implementações variadas. Nos geradores de Python, a execução do código é congeladas no ponto do rendimento. Quando o gerador é chamado (os métodos são discutidos abaixo) currículos de execução e depois congela no próximo rendimento.

yield fornece uma maneira fácil de Implementando o Protocolo Iterador, definido 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ê pode verificar com o Iterator Classe base abstrata da collections módulo.

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

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

Uma característica de um Iterator isso está exausto, você não pode reutilizar 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!']

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 abaixo - a partir do 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 aos subgeneradores, que serão explicados na seção a seguir sobre delegação cooperativa com sub-corototinas.

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 do received Variável, que apontará para os dados enviados ao 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 fazer fila o gerador com a função incorporada, next. Ele chamará o apropriado next ou __next__ Método, 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. (Envio 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-coroar yield from

Agora, lembre -se disso yield from está disponível no Python 3. Isso nos permite delegar coroutines a uma subcorutina:

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 ela pode ser usada 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 dentro PEP 380.

Outros métodos: feche e jogue

o close Método aumenta GeneratorExit No momento, a execução da função foi congelada. Isso também será chamado por __del__ Então você pode colocar qualquer código de limpeza onde você lida com o GeneratorExit:

>>> my_account.close()

Você também pode lançar uma exceção que pode ser tratada no gerador ou propagada de volta ao 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

Eu acredito que abordei todos os aspectos da seguinte pergunta:

O que faz o yield Palavra -chave em Python?

Acontece que yield faz muito. Tenho certeza de que poderia adicionar exemplos ainda mais completos a isso. Se você quiser mais ou tiver algumas críticas construtivas, me avise com o comentário abaixo.


Apêndice:

Crítica da resposta superior/aceita **

  • Está confuso sobre o que faz um iterável, apenas usando uma lista como exemplo. Veja minhas referências acima, mas em resumo: um iterável tem um __iter__ Método retornando um iterador. Um iterador Fornece uma .next (Python 2 ou .__next__ (Python 3) Método, que é implicitamente chamado por for loops até aumentar StopIteration, E uma vez que isso acontecer, 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 iterador, apenas confunde o assunto, e ainda não chegamos ao yield papel.
  • Dentro Controlando uma exaustão de um gerador Ele liga para o .next método, quando ele deve usar a função incorporada, next. Seria uma camada de indireção apropriada, porque seu código não funciona no Python 3.
  • Itetools? Isso não era relevante para o que yield sim.
  • Nenhuma discussão sobre os métodos que yield fornece junto com a nova funcionalidade yield from em Python 3. A resposta superior/aceita é uma resposta muito incompleta.

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

Atualmente, a gramática permite qualquer expressão em uma compreensão da 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

Como o rendimento é uma expressão, ele foi apresentado por alguns como interessantes usá -lo em compreensões ou expressão do gerador - apesar de citar nenhum caso de uso particularmente bom.

Os desenvolvedores do CPython Core são discutindo depreciar seu subsídio. Aqui está uma postagem relevante da lista de discussão:

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

On Sun, 29 de janeiro de 2017 às 16:39 Craig Rodrigues escreveu:

Estou bem com qualquer abordagem. Deixar as coisas do jeito que estão no Python 3 não é bom, IMHO.

Meu voto é ser um SyntaxError, pois você não está recebendo o que espera da sintaxe.

Eu concordo que é um lugar sensato para acabarmos, pois qualquer código que confia no comportamento atual é realmente muito inteligente para ser mantido.

Em termos de chegar lá, provavelmente queremos:

  • Sintaxe ou depreciação em 3.7
  • Aviso PY3K em 2.7.x
  • SyntaxError em 3.8

Saúde, Nick.

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

Além disso, existe um Excelente edição (10544) o que parece estar apontando na direção disso Nunca Ser uma boa idéia (Pypy, uma implementação do Python escrita em Python, já está levantando avisos de sintaxe.)

Resumindo, até que os desenvolvedores da Cpython nos dizem o contrário: Não coloque yield em uma expressão ou compreensão do gerador.

o return declaração em um gerador

Dentro Python 2:

Em uma função de gerador, o return A declaração não pode incluir um expression_list. Nesse contexto, um nu return indica que o gerador está feito e causará StopIteration ser criado.

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

Dentro Python 3:

Em uma função de gerador, o return a declaração indica que o gerador é feito e causará StopIteration ser criado. O valor retornado (se houver) é usado como um argumento para construir StopIteration e se torna o StopIteration.value atributo.

Notas de rodapé

  1. Os idiomas Clu, Sather e Icon foram referenciados na proposta de introduzir o conceito de geradores ao Python. A idéia geral é que uma função possa manter o estado interno e produzir pontos de dados intermediários sob demanda pelo usuário. Isso prometeu ser Superior no desempenho de outras abordagens, incluindo Threading Python, que nem sequer está disponível em alguns sistemas.

  2. Isso significa, por exemplo, que xrange objetos (range em Python 3) não são IteratorS, mesmo que sejam iteráveis, porque podem ser reutilizados. Como listas, suas __iter__ Métodos retornam objetos do iterador.

  3. yield foi originalmente introduzido como uma declaração, o que significa que só poderia aparecer no início de uma linha em um bloco de código. Agora yield cria uma expressão de rendimento.https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Essa mudança foi proposto Para permitir que um usuário envie dados para o gerador, assim como um pode recebê -los. Para enviar dados, é preciso ser capaz de atribuí -los a algo e, para isso, uma declaração simplesmente não funcionará.

yield é como return - ele retorna o que você diz para (como gerador). A diferença é que na próxima vez que você ligar para o gerador, a execução começa da última chamada para o yield declaração. Ao contrário do retorno, O quadro da pilha não é limpo quando ocorre um rendimento, no entanto, o controle é transferido de volta para o chamador, para que seu estado seja retomado na próxima vez que a função for chamada.

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

list.extend chama um iterador até que esteja exausto. No caso do exemplo de código que você postou, seria muito mais claro retornar uma tupla e anexá -la à lista.

Há uma coisa extra a mencionar: uma função que os rendimentos não precisam finalizar. Eu escrevi código como este:

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.

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

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

fazem isto:

def square_yield(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 disso.

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


yield é um açucarado maneira de dizer

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

Rendimento é passagem única: Você só pode iterar uma vez. Quando uma função tem um rendimento, chamamos de Função do gerador. E um iterador é o que ele retorna. Esses termos são reveladores. Perdemos a conveniência de um contêiner, mas obtemos o poder de uma série calculada conforme necessário e arbitrariamente por muito tempo.

Rendimento é preguiçoso, ele adia a computação. Uma função com um rendimento nele Na verdade, não é executado quando você chama. Ele retorna um objeto iterador Isso se lembra de onde parou. Cada vez que você liga next() No iterador (isso acontece em um loop para o loop), a execução de uma polegada para o próximo rendimento. return levanta a parada e termina a série (este é o fim natural de um loop para o forro).

Rendimento é versátil. Os dados não precisam ser armazenados juntos, 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 Passes múltiplos E a série não é muito longa, basta ligar list() nele:

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

Brilhante escolha da palavra yield Porque ambos os significados Aplique:

colheita - produzir ou fornecer (como na agricultura)

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

colheita - Dê um caminho ou abandone (como no poder político)

... renunciar à execução da CPU até que o iterador avança.

O rendimento fornece 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 a lista inteira na memória de uma só vez.Não é grande coisa para uma lista com 5 elementos, mas e se você quiser uma lista de 5 milhões?Isso não apenas consome muita memória, mas também leva muito tempo para ser construído no momento em que a função é chamada.

No segundo caso, bar apenas lhe dá um gerador.Um gerador é iterável - o que significa que você pode usá-lo em um for loop, 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 - desta forma, se você estiver usando um iterável para (digamos) contar até 50 bilhões, você não precisa contar até 50 bilhões no total de uma só vez e armazene os 50 bilhões de números para contar.

Novamente, este é um exemplo bastante inventado, você provavelmente usaria itertools se realmente quisesse contar até 50 bilhões.:)

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

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

A ideia principal é que o compilador/interpretador/o que quer que seja, faça alguns truques para que, no que diz respeito ao chamador, ele possa continuar chamando next() e ele continuará retornando valores - como se o método gerador estivesse pausado.Agora, obviamente, você não pode realmente "pausar" um método, então o compilador constrói uma máquina de estado para você lembrar onde está atualmente e como são as variáveis ​​locais, etc.Isso é muito mais fácil do que escrever você mesmo um iterador.

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

O yield instrução em Python retorna um gerador.Um gerador em Python é uma função que retorna continuações (e especificamente um tipo de corrotina, 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 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 é simples:é o estado de uma computação que ainda não foi concluída.Neste estado, são salvos os valores atuais das variáveis, as operações que ainda não foram executadas, e assim por diante.Então, em algum ponto posterior do programa, a continuação pode ser invocada, de modo que as variáveis ​​do programa sejam redefinidas para esse estado e as operações que foram salvas sejam executadas.

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

No estilo de passagem de continuação (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 gerencia explicitamente e passa para as sub-rotinas.Neste estilo, o estado do programa é representado por encerramentos (e pelas variáveis ​​que estão codificadas neles) em vez de variáveis ​​que residem em algum lugar da pilha.Funções que gerenciam o fluxo de controle aceitam continuação como argumentos (em algumas variações do CPS, as 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 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 escrever o arquivo em uma continuação (que pode ser potencialmente uma operação muito complexa com muitos detalhes para escrever) e então passa essa continuação (ou seja, como um primeiro passo). fechamento de classe) para outro operador que faz mais processamento e o chama se necessário.(Eu uso muito esse padrão de design na programação de GUI real, seja porque ele economiza linhas de código ou, mais importante, para gerenciar o fluxo de controle após o acionamento de eventos de GUI.)

O restante 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.Os geradores são um subtipo específico de continuação.Enquanto continuações são capazes em geral de salvar o estado de um computação (ou seja, a pilha de chamadas do programa), geradores só são capazes de salvar o estado da iteração durante um iterador.Embora esta 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 provavelmente não é 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 a potência dos geradores:se algo for um iterador, um gerador poderá salvar o estado de sua iteração.

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

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

Em qualquer momento yield é chamado, ele diz à função para retornar uma continuação.Quando a função é chamada novamente, ela começa de onde parou.Portanto, em pseudo-pseudocódigo (ou seja, não pseudocódigo, mas não código), o gerador next método é 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 o yield palavra-chave é na verdade um açúcar sintático para a função geradora 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 isso é apenas pseudocódigo e a implementação real dos 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 o yield palavra-chave.

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

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

  • Eu te chamo e digo que quero uma sequência de números que são produzidos de uma maneira específica, e eu informei qual é o algoritmo.
    Esta etapa corresponde a defInando a função do gerador, ou seja, a função contendo um yield.
  • Algum tempo depois, eu digo: "Ok, prepare -se para me dizer a sequência de números".
    Esta etapa corresponde a chamar a função do gerador que retorna um objeto gerador. Observe que você ainda não me diz nenhum número; Você apenas pega seu papel e lápis.
  • Eu pergunto: "Conte -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. É seu trabalho lembrar onde você estava, quais números você já disse e qual é o próximo número. Eu não me importo com os detalhes.
    Esta etapa corresponde à chamada .next() no objeto gerador.
  • … Repita a etapa anterior, até…
  • Eventualmente, você pode chegar ao fim. Você não me diz um número; Você apenas grita: "Segure seus cavalos! Estou pronto! Não há mais números!"
    Esta etapa corresponde ao objeto do gerador que encerrou seu trabalho e levantando um StopIteration exceção A função do gerador não precisa aumentar a exceção. É aumentado automaticamente quando a função termina ou emite um return.

É isso que um gerador faz (uma função que contém um yield); começa a executar, faz uma pausa sempre que faz um yield, e quando solicitado um .next() Valor continua a partir do ponto em que foi o último. Ele se encaixa perfeitamente por design com o Protocolo Iterador do Python, que descreve como solicitar sequencialmente valores.

O usuário mais famoso do protocolo Iterator é o for comando em python. Então, sempre que você faz:

for item in sequence:

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

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

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

Enquanto muitas respostas mostram por que você usaria um yield Para criar um gerador, há mais usos para yield. É muito fácil fazer uma coroutina, o que permite a passagem das informações entre dois blocos de código. Não vou repetir nenhum dos bons exemplos que já foram dados sobre o uso 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 através de qualquer código que tenha um yield. Cada vez que seu dedo atinge o yield, você tem que esperar por um next ou a send a ser inserido. Quando um next é chamado, você rastreia através do código até atingir o yield… O código à direita do yield é avaliado e devolvido ao chamador ... então você espera. Quando next é chamado novamente, você executa outro loop através do código. No entanto, você observará que em uma coroutina, yield também pode ser usado com um send… Que enviará um valor do chamador em a função de rendimento. Se um send é dado, então yield recebe o valor enviado e o cospe pelo lado esquerdo ... então o traço através do código progride até que você atinja o yield novamente (retornando o valor no final, como se se next foi 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()

Tem outro yield Uso e significado (desde Python 3.3):

yield from <expr>

A partir de PEP 380 - Sintaxe para delegar a um subgenerador:

Uma sintaxe é proposta para um gerador delegar parte de suas operações a outro gerador. Isso permite que uma seção do código que contém 'rendimento' seja fatorada e colocada em outro gerador. Além disso, o subgenerador pode retornar com um valor e o valor é disponibilizado ao gerador delegado.

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

Além disso isto Introduzirá (desde o Python 3.5):

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

Para evitar coroutinas sendo confundidas com um gerador comum (hoje yield é usado em ambos).

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

Presumo que você tenha aprendido o return declaração.

Como uma analogia, return e yield são gêmeos. return significa 'retorno e pare' enquanto 'rendimento' significa 'retorno, mas continue'

  1. Tente obter um 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ê prevalece felizmente, apenas implementa uma vez e sai.

  1. Lá vem yield

Substituir return com 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 corre uma vez e para, yield Executa vezes que você planeja. Você pode interpretar return Como return one of them, e yield Como return all of them. Isso é chamado iterable.

  1. Mais um passo, podemos reescrever yield 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 núcleo sobre yield.

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

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

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

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

Aqui estão alguns exemplos Python de como realmente implementar geradores como se Python não tivesse fornecido 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))

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 em vez de geradores (Porque ClosuresandObjectSeeeQuivalent)

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)

Eu ia publicar "Read Página 19 do 'Python: Reference essencial' 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 coroutinas como o dual de seu uso nas funções do gerador. Embora não seja o mesmo uso que o seu snippet de código, (yield) pode ser usado como expressão em uma função. Quando um chamador envia um valor para o método usando o send() método, então a coroutina será executada até o próximo (yield) declaração é encontrada.

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

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

Para implementar iteradores, geradores e pools de threads para execução simultânea, etc. Como Thunks (também chamados de funções anônimas), usa -se mensagens enviadas para um objeto de fechamento, que possui um despachante, e o despachante responde a "mensagens".

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

"próximo"é uma mensagem enviada para um fechamento, criado pelo"iter" ligar.

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

Aqui está uma demonstração que usa a estrutura dos R6Rs, mas a semântica é absolutamente idêntica à de Python. É o mesmo modelo de computação, e apenas uma mudança na sintaxe é necessária para reescrevê -la 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
->

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 de Python, mas me parece yield mantém a posição do fluxo do programa e o próximo loop inicia a partir da posição de "rendimento". 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

Aqui está uma imagem mental do que yield faz.

Gosto de pensar que um thread tem uma pilha (mesmo quando não é implementado dessa forma).

Quando uma função normal é chamada, ela coloca suas variáveis ​​locais na pilha, faz alguns cálculos, depois limpa a pilha e retorna.Os valores de suas variáveis ​​locais nunca mais serão vistos.

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

Portanto, é uma espécie de função congelada na qual o gerador está preso.

Quando next() é chamado posteriormente, ele recupera os pertences da função na pilha e a reanima.A função continua a calcular de onde parou, alheia ao fato de que acabou de passar uma eternidade em um armazenamento refrigerado.

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.O yield declaração pode ser inacessível, mas se estiver presente em qualquer lugar, muda a natureza daquilo com que estamos lidando.

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

Chamando yielderFunction() não executa seu código, mas transforma o código em um gerador.(Talvez seja uma boa ideia nomear essas coisas com o yielder prefixo 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']

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

Como todas as respostas sugerem, yield é usado para criar um gerador de sequência. É usado para gerar alguma sequência dinamicamente. Por exemplo, ao ler uma linha de arquivo por linha em uma rede, você pode usar o yield função da seguinte forma:

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

Você pode usá -lo no seu código da seguinte forma:

for line in getNextLines():
    doSomeThing(line)

Transferência de controle de execução Gotcha

O controle de execução será transferido de getNextLines () para o for loop quando o rendimento é executado. Assim, toda vez que GetNextLines () é invocado, a execução começa a partir do ponto em que foi interrompida da última vez.

Assim, em suma, 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"

Rendimento é um objeto

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

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

Mais importante, yield é um barreira.

Como a barreira no idioma CUDA, ela não transferirá o controle até que seja concluído.

Isto é, ele executará o código em sua função desde o início até que atinja yield. Em seguida, ele retornará o primeiro valor do loop.

Em seguida, todas as outras chamadas executarão 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.

(Minha resposta abaixo fala apenas da perspectiva de usar o gerador python, não o Implementação subjacente do mecanismo do gerador, que envolve alguns truques de manipulação de pilha e heap.)

Quando yield é usado em vez de um return Em uma função python, essa função é transformada em algo especial chamado generator function. Essa função retornará um objeto de generator modelo. o yield A palavra -chave é um sinalizador para notificar o compilador Python para tratar essa função especialmente. As funções normais terão rescindir assim que algum valor for retornado. Mas com a ajuda do compilador, a função do gerador pode ser pensado em como resumível. Ou seja, o contexto de execução será restaurado e a execução continuará a partir da última execução. Até você ligar explicitamente retorno, o que aumentará um StopIteration Exceção (que também faz parte do protocolo do iterador) ou chega ao final da função. Eu encontrei muitas referências sobre generator mas isso 1 de functional programming perspective é o mais digestível.

(Agora eu quero falar sobre a lógica por trás generator, e as iterator baseado em meu próprio entendimento. Espero que isso possa ajudá -lo a entender o motivação essencial de iterador e gerador. Esse conceito aparece em outros idiomas, como C#.)

Pelo que entendi, quando queremos processar um monte de dados, geralmente armazenamos os dados em algum lugar e depois o processamos um por um. Mas isso ingénuo A abordagem é problemática. Se o volume de dados for enorme, é caro armazená -los como um todo antes. Então, em vez de armazenar o data diretamente, por que não armazenar algum tipo de metadata Indiretamente, ou seja the logic how the data is computed.

Existem 2 abordagens para envolver esses metadados.

  1. A abordagem OO, embrulhamos os metadados as a class. Este é o chamado iterator quem implementa o protocolo do iterador (ou seja, o __next__(), e __iter__() métodos). Este também é o comumente visto Padrão de design do iterador.
  2. A abordagem funcional, embrulhamos os metadados as a function. Este é o chamado generator function. Mas sob o capô, o devolvido generator object ainda IS-A Iterador porque também implementa o protocolo do iterador.

De qualquer forma, um iterador é criado, ou seja, algum objeto que pode fornecer os dados que você deseja. A abordagem OO pode ser um pouco complexa. De qualquer forma, qual deles usar depende de você.

Em resumo, o yield a declaração transforma sua função em uma fábrica que produz um objeto especial chamado generator que envolve o corpo da sua função original. Quando o generator é iterado, ele executa sua função até atingir o próximo yield Em seguida, 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 sai 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

O poder vem do uso do gerador com um loop que calcula uma sequência, o gerador executa o loop para que a cada vez "produza" o próximo resultado do cálculo, dessa maneira calcula uma lista em tempo real, sendo o benefício a memória salvo para cálculos especialmente grandes

Diga que você queria criar um seu próprio range função que produz uma gama 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 use 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 a memória)
  • Esse código realmente atravessa essa matriz 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 chamado next() Executa a função até atingir uma declaração de 'rendimento' na qual ela para e 'produz' o valor ou atinge o final da função. Neste caso na primeira chamada, next() Executa até a declaração de rendimento e rendimento 'n', na próxima chamada, ele executará a declaração de incremento, voltará para o 'while', avaliará e, se for verdade, ele parará e cederá 'n' novamente, ele irá Continue assim até que a condição retorne falsa e o gerador salte até o final da função.

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

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

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

colheita (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 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 é return_dates() dá uma lista e yield_dates() dá um gerador.

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

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

o yield A palavra -chave simplesmente coleta os resultados retornados. Imagine yield Curti return +=

Aqui está um simples yield Abordagem baseada, para calcular a série Fibonacci, explicou:

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

Quando você entra no seu REPL e tenta chamá -lo, você obterá um resultado misterioso:

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

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

Usando o embutido next() função, você invoca 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 for Loop, a list Inicializador, a tuple Inicializador, 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 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 tuple Inicializador:

>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

Um gerador difere de uma função no sentido de que é preguiçoso. Isso faz isso mantendo seu estado local e permitindo que você retome sempre que precisar.

Quando você invoca pela primeira vez fib chamando -o:

f = fib()

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

Quando você solicita, gera o primeiro valor, direta ou indiretamente, ele executa todas as declarações que encontra, até que encontre um yield, então produz de volta o valor que você forneceu yield e pausas. Para um exemplo que melhor demonstra isso, vamos usar alguns print chamadas (substitua por print "text" Se 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!")

Agora você tem um objeto gerador esperando um comando para gerar um valor. Usar 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 cotados são o que é impresso. O resultado citado é o que é devolvido yield. Ligar next de novo 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 foi pausado em yield value e currículos a partir daí. A próxima mensagem é impressa e a pesquisa para o yield declaração para pausar no desempenho executado novamente (devido ao while ciclo).

Um exemplo fácil de entender o que é: 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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top