¿Qué hace la palabra clave de rendimiento?

¿Cuál es el uso de la palabra clave de yield en Python? ¿Qué hace?

Por ejemplo, trato de 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 

Y este es un marcador

 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 

¿Qué sucede cuando el método _get_child_candidates ? ¿Se devuelve la lista? ¿Artículo único? ¿Se llama de nuevo? ¿Cuándo se detendrán las llamadas de seguimiento?


1. El código está tomado de Jochen Schulz (jrschulz), quien creó una excelente biblioteca de Python para espacios métricos. Este es un enlace a la fuente completa: Módulo mspace .

8911
dado por Alex. 24 окт. S. 24 oct. 2008-10-24 01:21 '08 a la 1:21 am del 2008-10-24 01:21
@ 46 respuestas
  • 1
  • 2

Para comprender qué es un yield , es necesario comprender qué son los generadores. Y antes de los generadores vienen los iteradores.

iterado

Cuando creas una lista, puedes leer sus artículos uno por uno. Leer sus elementos uno por uno se llama iteración:

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

mylist es repetible. Cuando utiliza la comprensión de lista, crea una lista y, por lo tanto, puede repetirse:

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

Todo lo que puede usar " for... in... " es iterativo; lists , strings , archivos ...

Estas iteraciones son convenientes porque puede leerlas todo lo que quiera, pero mantiene todos los valores en la memoria, y esto no es siempre lo que quiere cuando tiene muchos valores.

Generadores

Los generadores son iteradores, un tipo de iteración que puede repetir solo una vez . Los generadores no almacenan todos los valores en la memoria, generan valores sobre la marcha :

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

Esto es lo mismo, excepto que usaste () lugar de [] . PERO, no puede hacerlo for я in mygenerator segunda vez, ya que los generadores solo se pueden usar una vez: calculan 0, luego se olvidan de ello y calculan 1, y terminan calculando 4, uno tras otro.

Ceder

yield es una palabra clave que se utiliza como return , excepto que la función devolverá un generador.

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

Este es un ejemplo inútil, pero es útil cuando sabe que su función devolverá un gran conjunto de valores que solo necesita leer una vez.

Para hacer frente al yield , debe comprender que cuando llama a una función, el código escrito en el cuerpo de la función no se inicia. La función devuelve solo el objeto del generador, es un poco complicado :-)

Luego, su código continuará desde donde lo dejó cada vez, for usar el generador.

Ahora la parte más difícil:

La primera vez que llama for llama a un objeto generador creado desde su función, ejecutará el código en su función desde el principio hasta que alcance el yield , y luego devuelve el primer valor del bucle. Luego, cada llamada subsiguiente iniciará de nuevo el bucle que escribió en la función y devolverá el siguiente valor hasta que se devuelva el valor.

El generador se considera vacío después de que se inicia la función, pero ya no entra en yield . Esto puede deberse al hecho de que el ciclo ha finalizado o al hecho de que ya no cumple con el "if/else" .


Su código explicado

Generador:

 # 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 

Suscriptor

 # 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 contiene varias partes inteligentes:

  • El ciclo se repite en la lista, pero la lista se expande durante la iteración del bucle :-) Esta es una forma breve de revisar todos estos datos anidados, incluso si es un poco peligroso, ya que puede obtener un bucle infinito. En este caso, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) agota todos los valores del generador, pero continúa creando nuevos objetos generadores que generarán valores distintos a los anteriores, ya que no se aplica al mismo nodo .

  • El método extend() es un método de un objeto de lista que espera la iteración y agrega sus valores a la lista.

Por lo general le damos una lista:

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

Pero en tu código recibe un generador, lo cual es bueno porque:

  1. No necesitas leer los valores dos veces.
  2. Puede tener muchos hijos y no quiere que todos se guarden en la memoria.

Y funciona porque a Python no le importa si el argumento del método es una lista o no. Python está esperando la iteración, por lo que funcionará con cadenas, listas, tuplas y generadores. Esto se llama pato y es una de las razones por las que Python es tan genial. Pero esta es otra historia para otra pregunta ...

Puede detenerse aquí o leer un poco para ver el uso avanzado del generador:

Control de agotamiento del generador.

 >>> 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 Python 3, use print(corner_street_atm.__next__()) o print(next(corner_street_atm))

Esto puede ser útil para varias cosas, como controlar el acceso a un recurso.

Itertools, tu mejor amigo.

El módulo de itertools contiene funciones especiales para gestionar iteraciones. ¿Alguna vez has querido duplicar un generador? ¿Una cadena de dos generadores? ¿Agrupar valores en una lista anidada con una línea? Map/Zip sin crear otra lista?

Entonces sólo import itertools .

Un ejemplo Echemos un vistazo a los posibles procedimientos de llegada para las carreras de caballos:

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

Comprender los mecanismos de iteración internos.

La iteración es un proceso que implica iteraciones (implementación del __iter__() ) e iteradores (implementación del __next__() ). Las iteraciones son objetos de los que puede obtener un iterador. Los iteradores son objetos que te permiten repetir iteraciones.

Hay más sobre esto en este artículo sobre cómo trabajar en bucle .

13022
24 окт. La respuesta está dada por e-satis 24 oct. 2008-10-24 01:48 '08 a 1:48 2008-10-24 01:48

Etiqueta al yield Grocking.

Cuando vea una función con yield , use este simple truco para comprender lo que sucederá:

  1. Inserte la línea del result = [] al principio de la función.
  2. Reemplace cada yield expr con result.append(expr) .
  3. Inserte el resultado de la línea de return result en la parte inferior de la función.
  4. Yay - no más declaraciones de yield ! Lee y descubre el código.
  5. Compara la función con la definición original.

Esta técnica puede darle una idea de la lógica de una función, pero lo que realmente sucede con un yield es significativamente diferente de lo que sucede en el enfoque basado en listas. En muchos casos, el enfoque de rendimiento será mucho más eficiente y más rápido. En otros casos, este truco se atascará en un bucle sin fin, incluso si la función original funciona bien. Sigue leyendo para saber más ...

No confunda sus iteradores, iteradores y generadores.

Primero, el protocolo iterador - cuando escribes

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

Python realiza los siguientes dos pasos:

  1. Obtiene un iterador para mylist :

    Llamar a iter(mylist) → devuelve un objeto con el método next() (o __next__() en Python 3).

    [Este es un paso del que la mayoría de la gente se olvida de hablar]

  2. Utiliza un iterador para bucear elementos:

    Continúe llamando al método next() en el iterador devuelto desde el paso 1. El valor de retorno next() asigna a x y se ejecuta el cuerpo del bucle. Si la excepción StopIteration llama desde adentro next() , significa que no hay más valores en el iterador y el ciclo termina.

La verdad es que Python realiza los dos pasos anteriores en cualquier momento cuando quiere recorrer el contenido de un objeto, por lo que puede ser un bucle for, pero también puede ser un código como otherlist.extend(mylist) (donde otherlist es una lista de Python )

border=0

Aquí, mylist es iterativo, ya que implementa el protocolo iterador. En una clase definida por el usuario, puede implementar el __iter__() para hacer que sus instancias de clase sean iterativas. Este método debería devolver un iterador. Un iterador es un objeto con un método next() . Puede implementar tanto __iter__() como next() en la misma clase y tener __iter__() devolviéndose a self . Esto funcionará para casos simples, pero no cuando desee que dos iteradores realicen un ciclo del mismo objeto al mismo tiempo.

Así que en el protocolo iterador, muchos objetos implementan este protocolo:

  1. Listas incorporadas, diccionarios, tuplas, conjuntos, archivos.
  2. Clases personalizadas que implementan __iter__() .
  3. Generadores

Tenga en cuenta que el bucle for no sabe con qué objeto está tratando; simplemente sigue el protocolo del iterador y está feliz de obtener elemento por elemento cuando llama a next() . Las listas integradas devuelven sus elementos uno por uno, los diccionarios devuelven las claves una por una, los archivos devuelven cadenas una por una, etc. Y los generadores regresan ... bueno, cuando llega el yield :

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

En lugar de las yield , si hubiera tres operadores f123() en f123() solo el primero y la función f123() . Pero f123() no f123() una función ordinaria. Cuando f123() , no devuelve ningún valor en las declaraciones de rendimiento! Devuelve un objeto generador. Además, la función en realidad no sale, entra en un estado de espera. Cuando el bucle for intenta realizar un bucle en el objeto generador, la función regresa de su estado en pausa en la siguiente línea después del yield resultado devuelto anteriormente, ejecuta la siguiente línea de código, en este caso la yield , y la devuelve como el siguiente elemento. Esto sucede hasta que se libera la función, y en este momento el generador StopIteration y el ciclo StopIteration .

Por lo tanto, el objeto generador es similar a un adaptador: en un extremo muestra un protocolo de iterador, que proporciona __iter__() y next() para mantener un bucle for en buen estado. Sin embargo, en el otro extremo, inicia una función que es suficiente para obtener el siguiente valor y la vuelve a poner en modo de espera.

¿Por qué usar generadores?

Por lo general, puede escribir código que no usa generadores, pero implementa la misma lógica. Una opción es usar la lista de trucos temporales que mencioné anteriormente. Esto no funcionará en todos los casos, por ejemplo, si tiene bucles infinitos, o puede llevar a un uso ineficiente de la memoria cuando tiene una lista realmente larga. Otro enfoque es implementar la nueva clase iterativa SomethingIter que guarda el estado en los elementos de la instancia y realiza el siguiente paso lógico en ella mediante el método next() (o __next__() en Python 3). Dependiendo de la lógica, el código dentro del método next() puede parecer muy complicado y propenso a errores. Aquí los generadores proporcionan una solución limpia y simple.

1744
26 окт. respuesta dada por el usuario28409 26 de octubre 2008-10-26 00:22 '08 a las 0:22 2008-10-26 00:22

Piénsalo así:

Un iterador es solo un término elegante para un objeto que tiene un método next (). Entonces, la función de rendimiento final se ve así:

Versión original

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

Esto es básicamente lo que hace el intérprete de Python con el código anterior:

 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 comprender mejor lo que está sucediendo detrás de escena, el bucle for se puede reescribir de la siguiente manera:

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

¿Tiene más sentido o simplemente te confunde? :)

Debo señalar que esta es una simplificación para fines ilustrativos. :)

441
24 окт. Responder a Jason Baker el 24 de octubre 2008-10-24 01:28 '08 a la 1:28 2008-10-24 01:28

La palabra clave de yield se reduce a dos hechos simples:

  1. Si el compilador detecta la palabra clave de yield cualquier lugar dentro de una función, esta función ya no se devuelve a través de la return . En su lugar, devuelve inmediatamente un objeto de lista de espera perezoso, denominado generador.
  2. El generador se repite. ¿Qué es repetible? Esto es algo así como un set list range o vista de dictado con un protocolo incorporado para visitar cada elemento en un orden específico.

En pocas palabras: un generador es una lista perezosa, que aumenta gradualmente , y las yield permiten usar la función de notación para programar los valores de lista que el generador debería generar 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]] 

un ejemplo

Definamos la función makeRange que es similar al range Python. La makeRange(n) GENERADOR:

 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 hacer que el generador devuelva inmediatamente los valores pendientes, puede pasarlo a list() (como cualquier otro iterativo):

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

Comparando un ejemplo con "solo devolviendo una lista"

El ejemplo anterior se puede ver simplemente creando una lista a la que agrega y devuelve:

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

Sin embargo, hay una diferencia significativa; Vea la última sección.


¿Cómo se pueden usar generadores?

Iterado es la última parte de la comprensión de la lista, y todos los generadores son iterativos, por lo que a menudo se usan así:

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

Para comprender mejor los generadores, puede jugar con el módulo de itertools (asegúrese de usar chain.from_iterable y no chain con una garantía para chain.from_iterable ). Por ejemplo, incluso puedes usar generadores para implementar listas perezosas infinitamente largas, como itertools.count() . Puede implementar su propia def enumerate(iterable): zip(count(), iterable) o, alternativamente, hacer esto usando la palabra clave de yield en un bucle while.

Tenga en cuenta que los generadores se pueden usar para muchos otros propósitos, como implementar coroutines, programación no determinista u otras cosas elegantes. Sin embargo, la vista de "listas perezosas", que represento aquí, es el área de uso más común que encontrará.


Detrás de las escenas

Así es como funciona el Protocolo de Iteración de Python. Eso es lo que sucede cuando haces una list(makeRange(5)) . Esto es lo que describo anteriormente como una "lista extra, perezosa".

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

La next() función .next() simplemente llama a .next() objetos .next() , que forma parte del "protocolo de iteración" y se produce en todos los iteradores. Puede usar manualmente la función next() (y otras partes del protocolo de iteración) para implementar cosas inusuales, generalmente a expensas de la legibilidad, así que trate de no hacer esto ...


pequeñas cosas

Generalmente, la mayoría de las personas no se preocupan por las siguientes diferencias y probablemente querrán dejar de leer aquí.

En Python, iterativo es cualquier objeto que "entiende el concepto de un bucle for", por ejemplo, una lista [1,2,3] , y un iterador es una instancia específica del bucle for solicitado, por ejemplo [1,2,3].__iter__() . El generador es exactamente igual que cualquier iterador, excepto por la forma en que fue escrito (con la sintaxis de la función).

Cuando solicita un iterador de la lista, crea un nuevo iterador. Sin embargo, cuando solicita un iterador de un iterador (lo que rara vez hace), simplemente le da su copia.

Entonces, en el improbable caso de que no puedas hacer algo así ...

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

... entonces recuerda que el generador es un iterador; es decir, un uso del tiempo. Si desea reutilizarlo, debe myRange(...) llamar a myRange(...) . Si necesita usar el resultado dos veces, convierta el resultado en una lista y guárdelo en la variable x = list(myRange(5)) . Aquellos que absolutamente necesitan clonar un generador (por ejemplo, que realiza una metaprogramación terriblemente pirata) pueden usar itertools.tee si es absolutamente necesario, ya que la oferta de los estándares Python PEP para el iterador se ha retrasado.

378
19 июня '11 в 9:33 2011-06-19 09:33 la respuesta se da ninjagecko 19 de junio de 2011 a las 9:33 2011-06-19 09:33

¿Qué hace la palabra clave de yield en python?

Esquema de Respuesta / Resumen

  • La función de yield en la llamada devuelve un generador .
  • Генераторы являются итераторами, потому что они реализуют протокол итератора , поэтому вы можете выполнять итерации по ним.
  • Генератору также может быть отправлена информация , что делает его концептуально сопрограммой .
  • В 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.