O que a palavra-chave yield faz?

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

Por exemplo, tento 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 isso é um discador

 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 ? A lista é devolvida? Item único? É chamado de novo? Quando as chamadas de acompanhamento serão interrompidas?


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

8911
dada por Alex. 24 окт. S. 24 de outubro 2008-10-24 01:21 '08 em 1:21 am 2008-10-24 01:21
@ 46 respostas
  • 1
  • 2

Para entender o que é um yield , você precisa entender o que são geradores. E antes que os geradores venham iteradores.

iterado

Quando você cria uma lista, você pode ler seus itens um por um. A leitura de seus elementos, um por um, é chamada de iteração:

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

mylist é repetitivo. Quando você usa a compreensão de lista, cria uma lista e, portanto, pode ser repetida:

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

Tudo o que você pode usar " for... in... " é iterativo; lists , strings , arquivos ...

Essas iterações são convenientes porque você pode ler quantas quiser, mas mantém todos os valores na memória, e isso nem sempre é o que você deseja quando tem muitos valores.

Geradores

Geradores são iteradores, um tipo de iteração que você pode repetir apenas uma vez . Os geradores não armazenam todos os valores na memória, eles geram valores dinamicamente :

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

Isso é o mesmo, exceto que você usou () vez de [] . MAS, você não pode fazer isso for я in mygenerator segunda vez, já que os geradores podem ser usados ​​apenas uma vez: eles calculam 0, depois esquecem e calculam 1, e acabam calculando 4, um após o outro.

Rendimento

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

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

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

Para lidar com o yield , você deve entender que quando você chama uma função, o código escrito no corpo da função não é iniciado. A função retorna apenas o objeto gerador, é um pouco complicado :-)

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

Agora a parte mais difícil:

A primeira vez que você chama for chama um objeto gerador criado a partir de sua função, ele irá executar o código em sua função desde o início até atingir o yield e, em seguida, retorna o primeiro valor do loop. Em seguida, cada chamada subsequente iniciará o loop que você gravou na função novamente e retornará o próximo valor até que o valor seja retornado.

O gerador é considerado vazio depois que a função é iniciada, mas não entra mais no yield . Isso pode ser devido ao fato de o ciclo ter terminado ou devido ao fato de você não mais satisfazer o "if/else" .


Seu código explicado

Gerador:

 # Here you create the method of the node object that will return the generator def _get_child_candidates(self, distance, min_dist, max_dist): # Here is the code that will be called each time you use the generator object: # If there is still a child of the node object on its left # AND if distance is ok, return the next child if self._leftchild and distance - max_dist < self._median: yield self._leftchild # If there is still a child of the node object on its right # AND if distance is ok, return the next child if self._rightchild and distance + max_dist >= self._median: yield self._rightchild # If the function arrives here, the generator will be considered empty # there is no more than two values: the left and the right children 

Assinante:

 # 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 ciclo é repetido na lista, mas a lista se expande durante a iteração do loop :-) Esta é uma maneira breve de passar por todos esses dados aninhados, mesmo que seja um pouco perigoso, já que você pode obter 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 objetos geradores que gerarão valores diferentes dos anteriores, já que ele não se aplica ao mesmo nó .

  • O método extend() é um método de um objeto de lista que aguarda por iteração e adiciona seus valores à lista.

Geralmente nós damos a ele uma lista:

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

Mas no seu código ele pega 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 sejam guardados na memória.

E funciona porque o Python não se importa se o argumento do método é uma lista ou não. Python está esperando por iteração, então ele irá trabalhar com strings, listas, tuplas e geradores! Isso é chamado de duck e é uma das razões pelas quais o Python é tão legal. Mas esta é outra história para outra pergunta ...

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

Controle de exaustão do gerador

 >>> class Bank(): # Let create a bank, building ATMs ... crisis = False ... def create_atm(self): ... while not self.crisis: ... yield "$100" >>> hsbc = Bank() # When everything 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 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, use print(corner_street_atm.__next__()) ou print(next(corner_street_atm))

Isso 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 gerenciar iterações. Você já quis duplicar um gerador? Uma corrente de dois geradores? Agrupar valores em uma lista aninhada com uma linha? Map/Zip sem criar outra lista?

Em seguida, basta import itertools .

Um exemplo? Vamos dar uma olhada nos possíveis procedimentos de chegada para corridas de cavalos:

 >>> horses = [1, 2, 3, 4] >>> races = itertools.permutations(horses) >>> print(races) <itertools.permutations object at 0xb754f1dc> >>> print(list(itertools.permutations(horses))) [(1, 2, 3, 4), (1, 2, 4, 3), (1, 3, 2, 4), (1, 3, 4, 2), (1, 4, 2, 3), (1, 4, 3, 2), (2, 1, 3, 4), (2, 1, 4, 3), (2, 3, 1, 4), (2, 3, 4, 1), (2, 4, 1, 3), (2, 4, 3, 1), (3, 1, 2, 4), (3, 1, 4, 2), (3, 2, 1, 4), (3, 2, 4, 1), (3, 4, 1, 2), (3, 4, 2, 1), (4, 1, 2, 3), (4, 1, 3, 2), (4, 2, 1, 3), (4, 2, 3, 1), (4, 3, 1, 2), (4, 3, 2, 1)] 

Entendendo mecanismos internos de iteração

Iteração é um processo que implica em iterações (implementando o __iter__() ) e iteradores (implementando o __next__() ). Iterações são quaisquer objetos dos quais você pode obter um iterador. Iteradores são objetos que permitem repetir iterações.

Há mais sobre isso neste artigo sobre como fazer um loop de trabalho .

13022
24 окт. A resposta é dada por e-satis 24 de outubro. 2008-10-24 01:48 '08 às 1:48 2008-10-24 01:48

Etiqueta para o yield Grocking

Quando você vê uma função com yield , use este truque simples para entender o que acontecerá:

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

Essa técnica pode dar uma idéia da lógica de uma função, mas o que realmente acontece com um yield é significativamente diferente do que acontece na abordagem baseada em lista. Em muitos casos, a abordagem de rendimento será muito mais eficiente e mais rápida. Em outros casos, esse truque ficará preso em um loop infinito, mesmo se a função original funcionar bem. Leia para saber mais ...

Não confunda seus iteradores, iteradores e geradores.

Primeiro, o protocolo do iterador - quando você escreve

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

O Python executa as duas etapas a seguir:

  1. Obtém um iterador para mylist :

    Calling iter(mylist) → retorna um objeto com o método next() (ou __next__() no Python 3).

    [Este é um passo que a maioria das pessoas esquece de falar]

  2. Usa um iterador para elementos de loop:

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

A verdade é que o Python executa as duas etapas acima a qualquer momento quando deseja iterar sobre o conteúdo de um objeto - portanto pode ser um loop for, mas também pode ser código como otherlist.extend(mylist) (onde otherlist é uma lista do Python )

border=0

Aqui, mylist é iterativa, pois implementa o protocolo do iterador. Em uma classe definida pelo usuário, você pode implementar o __iter__() para tornar as instâncias de sua classe interativas. Este método deve retornar um iterador. Um iterador é um objeto com um método next() . Você pode implementar __iter__() e next() na mesma classe e ter __iter__() retornando self . Isso funcionará para casos simples, mas não quando você quiser que dois iteradores façam o ciclo do mesmo objeto ao mesmo tempo.

Portanto, no protocolo do iterador, muitos objetos implementam esse protocolo:

  1. Listas, dicionários, tuplas, conjuntos e arquivos integrados.
  2. Classes personalizadas que implementam __iter__() .
  3. Geradores

Observe que o loop for não sabe com qual objeto está lidando - ele apenas segue o protocolo do iterador e fica feliz em obter elemento por elemento ao chamar next() . As listas internas retornam seus itens um a um, os dicionários retornam as chaves uma por uma, os arquivos retornam as seqüências de caracteres uma por uma, etc. E os geradores retornam ... bem, quando o yield vem:

 def f123(): yield 1 yield 2 yield 3 for item in f123(): print item 

Em vez das yield , se houver três operadores de return f123() em f123() apenas a primeira e a função f123() . Mas f123() não f123() uma função comum. Quando f123() , não retorna nenhum valor nas declarações de rendimento! Retorna um objeto gerador. Além disso, a função não sai realmente - ela entra em estado de espera. Quando o loop for tenta executar um loop no objeto gerador, a função retorna de seu estado pausado na próxima linha após o resultado retornado anteriormente, executa a próxima linha de código, nesse caso, a yield , e a retorna como o próximo item. Isso acontece até que a função seja liberada e, neste momento, o gerador StopIteration e o ciclo StopIteration .

Assim, o objeto gerador é semelhante a um adaptador - em uma extremidade, ele demonstra um protocolo iterador, fornecendo __iter__() e next() para manter um loop for em boas condições. No outro extremo, no entanto, ele inicia uma função que é suficiente para obter o próximo valor e o coloca de volta no modo de espera.

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 a lista de truques temporários que mencionei anteriormente. Isso não funcionará em todos os casos, por exemplo, se você tiver loops infinitos, ou pode levar a um uso ineficiente da memória quando você tiver uma lista realmente longa. Outra abordagem é implementar a nova classe iterativa SomethingIter que salva o estado nos elementos da instância e executa a próxima etapa lógica nela, pelo método next() (ou __next__() no Python 3). Dependendo da lógica, o código dentro do método next() pode parecer muito complicado e propenso a erros. Aqui os geradores fornecem uma solução limpa e simples.

1744
26 окт. Resposta dada por user28409 26 de out 2008-10-26 00:22 '08 às 0:22 2008-10-26 00:22

Pense nisso assim:

Um iterador é apenas um termo sofisticado para um objeto que possui um método next (). Então, a função de rendimento finalmente se parece com isso:

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 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 entender melhor o que está acontecendo nos bastidores, o loop for pode ser reescrito da seguinte maneira:

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

Isso faz mais sentido ou apenas confunde você? :)

Devo salientar que isso é uma simplificação para fins ilustrativos. :)

441
24 окт. Em resposta a Jason Baker em 24 de outubro 2008-10-24 01:28 '08 em 1:28 2008-10-24 01:28

A palavra-chave yield resume-se a dois fatos simples:

  1. Se o compilador detectar a palavra-chave yield qualquer lugar dentro de uma função, essa função não será mais retornada por meio da return . Em vez disso, ele imediatamente retorna um objeto de lista de espera lenta, chamado gerador.
  2. O gerador é repetido. O que é repetível? Isso é algo como um range set list ou uma visualização de dit com um protocolo incorporado para visitar cada item em uma ordem específica.

Resumindo: um gerador é uma lista preguiçosa, que aumenta gradualmente , e as yield permitem que você use a função de notação para programar os valores da lista que o gerador deve produzir 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]] 

um exemplo

Vamos definir a função makeRange que é semelhante ao range do Python. A makeRange(n) 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 fazer com que o gerador retorne imediatamente valores pendentes, você pode passá-lo para list() (como qualquer outro iterativo):

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

Comparando um exemplo com "apenas retornando uma lista"

O exemplo acima pode ser visto como simplesmente 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 diferença significativa; Veja a última seção.


Como você pode usar geradores

Iterated é a última parte de entender a lista, e todos os geradores são iterativos, então eles são freqüentemente usados ​​assim:

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

Para entender melhor os geradores, você pode brincar com o módulo itertools (certifique-se de usar chain.from_iterable e não chain com uma garantia para chain.from_iterable ). Por exemplo, você pode até usar geradores para implementar listas itertools.count() infinitamente longas, como itertools.count() . Você pode implementar seu próprio def enumerate(iterable): zip(count(), iterable) ou, como alternativa, fazer isso usando a palavra-chave yield em um loop while.

Observe que os geradores podem ser usados ​​para muitas outras finalidades, como a implementação de corrotinas, programação não determinística ou outras coisas elegantes. No entanto, o modo de exibição de "listas preguiçosas", que represento aqui, é a área de uso mais comum que você encontrará.


Nos bastidores

É assim que funciona o Protocolo de Iteração Python. É o que acontece quando você faz uma list(makeRange(5)) . Isso é o que eu descrevi anteriormente como uma "lista extra 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 .next() next() simplesmente chama objetos .next() , que é parte do "protocolo de iteração" e ocorre em todos os iteradores. Você pode manualmente usar a função next() (e outras partes do protocolo de iteração) para implementar coisas incomuns, geralmente à custa da legibilidade, então tente não fazer isso ...


pequenas coisas

Normalmente, a maioria das pessoas não se importa com as diferenças a seguir e provavelmente vai querer parar de ler aqui.

Em Python, iterativo é qualquer objeto que "entende o conceito de um loop for", por exemplo, uma lista [1,2,3] , e um iterador é uma instância específica do loop solicitado, por exemplo [1,2,3].__iter__() . O gerador é exatamente igual a qualquer iterador, exceto pelo modo como foi escrito (com a sintaxe da função).

Quando você solicita um iterador da lista, ele cria um novo iterador. No entanto, quando você solicitar um iterador de um iterador (o que você raramente faz), ele simplesmente fornece sua cópia.

Então, no caso improvável de você não ser capaz de fazer algo assim ...

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

... então lembre-se de que o gerador é um iterador; ou seja, uma vez uso. Se você quiser reutilizá-lo, você deve myRange(...) chamar myRange(...) . Se você precisar usar o resultado duas vezes, converta o resultado em uma lista e salve-o na variável x = list(myRange(5)) . Aqueles que absolutamente precisam clonar um gerador (por exemplo, que executa metaprogramação terrivelmente hacker) podem usar itertools.tee se for absolutamente necessário, uma vez que a oferta de padrões Python PEP para o iterador foi adiada.

378
19 июня '11 в 9:33 2011-06-19 09:33 a resposta é dada ninjagecko junho 19 '11 em 9:33 2011-06-19 09:33

O que a palavra-chave yield faz em python?

Esquema de Resposta / Resumo

  • A função de yield na chamada retorna um gerador .
  • Os geradores são iteradores porque implementam um protocolo iterador , para que você possa iterá-los.
  • Informações também podem ser enviadas para o gerador, tornando-se conceitualmente uma co - rotina .
  • В Python 3 вы можете делегировать от одного генератора другому в обоих направлениях с помощью yield from .
  • (Приложение критикует пару @, включая верхний, и обсуждает использование return в генераторе.)

Генераторы:

yield допустим только внутри определения функции, и включение yield в определение функции заставляет его возвращать генератор.

Идея для генераторов исходит из других языков (см. Сноску 1) с различными реализациями. В Python Generators выполнение кода заморожено в точке выхода. Когда вызывается генератор (методы обсуждаются ниже), выполнение возобновляется, а затем останавливается при следующем выходе.

yield предоставляет простой способ реализации протокола итератора , который определяется следующими двумя методами: __iter__ и next (Python 2) или __next__ (Python 3). Оба эти метода делают объект итератором, который можно проверить типом с помощью абстрактного базового класса Iterator из модуля 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 an iterable True >>> hasattr(gen, 'next') # and with .next (.__next__ in Python 3) True # implements the iterator protocol.