4531 votos

¿Qué significa la palabra clave yield hacer en Python?

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

Por ejemplo, estoy tratando de entender este código (**):

def node._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 el visitante:

result, candidates = list(), [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é ocurre cuando el método _get_child_candidates se llama? Una lista se devuelve? Un solo elemento se devuelve? Es llamado de nuevo? Cuando las llamadas posteriores no?

** El código proviene de Jochen Schulz (jrschulz), que hizo una gran biblioteca de Python para la métrica de los espacios. Este es el enlace a la fuente completa: Módulo mspace.

7050voto

e-satis Puntos 146299

Para entender lo yield hace, usted debe entender lo que los generadores. Y antes de que los generadores vienen iterables.

Iterables

Al crear una lista, usted puede leer sus artículos uno por uno, y se llama iteración:

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

Milista es un iterable. Cuando se utiliza una lista de comprensión, de crear una lista, y así un iterable:

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

Todo lo que usted puede hacer uso de "......" es un iterable: listas, cadenas, archivos... Estos iterables son útiles porque se puede leer tanto como usted desea, pero se almacenan todos los valores en la memoria y no siempre es lo que usted desea cuando usted tiene un montón de valores.

Generadores de

Los generadores son iteradores, pero sólo puede iterar sobre ellos de una vez. Es porque no almacenar todos los valores en la memoria, que generan los valores sobre la marcha:

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

Es justo el mismo, excepto que usted utilizó () en lugar de []. PERO, no se puede realizar for i in mygenerator la segunda vez desde que los generadores sólo puede ser usado una vez: calcular 0, luego olvidarse de él y calcular 1, y al final el cálculo de 4, uno por uno.

Rendimiento

Yield es una palabra clave que se utiliza como return, a excepción de 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

Aquí es un inútil ejemplo, pero es útil cuando usted sabe que su función devolverá un enorme conjunto de valores que usted sólo tendrá que leer una vez.

Para dominar yield, usted debe entender que cuando se llama a la función, el código que ha escrito en el cuerpo de la función no se ejecuta. La función sólo devuelve el generador de objeto, esto es un poco complicado :-)

A continuación, el código se ejecutará cada vez que el for utiliza el generador.

Ahora la parte difícil:

La primera vez que el for llama al generador objeto creado a partir de su función, se ejecuta el código en su función desde el principio hasta que llegue yield, entonces se devolverá el primer valor del bucle. A continuación, cada llamada se ejecutará el bucle en el que se han escrito en la función una vez más, y devolver el siguiente valor, hasta que no hay un valor de retorno.

El generador se considera vacío una vez que se ejecuta la función, pero no llegue a producir nunca más. Puede ser debido a que el bucle había llegado a su fin, o porque no satisfacer a un "if/else" más.

Su explicación del código

Generador:

# Here you create the method of the node object that will return the generator
def node._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

Persona que llama:

# 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 varios inteligente partes:

  • El bucle itera en una lista, pero la lista se expande, mientras que el bucle se reiteró :-) Es una manera concisa para ir a través de todos estos datos anidados, incluso si es un poco peligroso, ya que puede acabar con un bucle infinito. En este caso, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) agota todos los valores del generador, pero while mantiene la creación de nuevo generador de objetos que se producen valores diferentes de los anteriores, ya que no se aplica en el mismo nodo.

  • La extend() método es un objeto de la lista de método que espera un iterable y agrega sus valores a la lista.

Generalmente pasamos una lista:

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

Pero en el código que se obtiene un generador, lo cual es bueno porque:

  1. Usted no necesita leer los valores de dos veces.
  2. Usted puede tener un montón de hijos y no los quieren todos los almacenados en la memoria.

Y esto es así porque Python no importa si el argumento de un método es una lista o no. Python espera iterables así que va a trabajar con cadenas de caracteres, listas, tuplas y generadores! Esto se llama duck typing y es una de la razón por la que Python es tan cool. Pero esta es otra historia para otra pregunta...

Usted puede parar aquí, o leer un poco para ver un uso avanzado del generador:

El control de un generador de agotamiento

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

Puede ser útil para varias cosas como el control de acceso a un recurso.

Itertools, su mejor amigo

El itertools módulo contiene funciones especiales para manipular iterables. Nunca desee duplicar un generador? Cadena de dos generadores? Grupo de valores en una lista anidada con un forro? Mapa / Zip sin necesidad de crear otra lista?

A continuación, sólo import itertools.

Un ejemplo? Vamos a ver las posibles órdenes de la llegada de un 4 de 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)]

La comprensión de los mecanismos internos de la iteración

La iteración es un proceso que implica iterables (aplicación de la __iter__() método) y los iteradores (aplicación de la __next__()método). Iterables son los objetos que puede obtener un iterador. Los iteradores son objetos que permiten iterar sobre iterables.

Más sobre él en este artículo acerca de cómo funciona el bucle for de trabajo.

917voto

user28409 Puntos 6460

Acceso directo a Grokking yield

Cuando vea una función con yield declaraciones, aplicar este truco fácil para entender lo que va a suceder:

  1. Insertar una línea result = [] en el inicio de la función.
  2. Sustituir cada yield expr con result.append(expr).
  3. Insertar una línea return result en la parte inferior de la función.
  4. Yay - no más yield declaraciones! Leer y entender el código.
  5. Función de comparación para la definición original.

Este truco puede dar una idea de la lógica detrás de la función, pero lo que en realidad sucede con yield es significativamente diferente que lo que sucede en la lista. En muchos casos, el rendimiento de enfoque va a ser mucho más eficiente de la memoria y más rápido. En otros casos, este truco de obtener atascado en un bucle infinito, incluso a pesar de que la función original funciona bien. Sigue leyendo para saber más...

No confundir el Iterables, Iteradores y Generadores

En primer lugar, el protocolo de iteración - al escribir

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

Python realiza los dos pasos siguientes:

  1. Consigue un iterador para mylist:

    Llame iter(mylist) -> devuelve un objeto con un next() método (o __next__() en Python 3).

    [Este es el paso que la mayoría de la gente se olvide de decirle a usted acerca de]

  2. Utiliza el iterador para recorrer los elementos:

    Seguir llamando la next() método en el iterador devuelto desde el paso 1. El valor de retorno next() está asignado a x y el cuerpo del bucle se ejecuta. Si una excepción StopIteration está planteado desde dentro, next(), significa que no hay más valores en el iterador y se sale del bucle.

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

Aquí mylist es un iterable porque implementa el protocolo de iteración. En una clase definida por el usuario, puede implementar la __iter__() método de crear instancias de la clase iterable. Este método devuelve un iterador. Un iterador es un objeto con un next() método. Es posible implementar __iter__() y next() en la misma clase, y tiene __iter__() de retorno self. Esto funcionará para los casos más sencillos, pero no cuando se quiere que dos iteradores bucle sobre el mismo objeto al mismo tiempo.

De manera que el iterador protocolo, muchos de los objetos de la aplicación de este protocolo:

  1. Incorporado en las listas, diccionarios, tuplas, conjuntos de archivos.
  2. Clases definidas por el usuario que implementar __iter__().
  3. Los generadores.

Tenga en cuenta que un for bucle no se sabe qué tipo de objeto se trata - simplemente sigue el protocolo de iteración, y es feliz para obtener elemento tras elemento como se llama next(). Incorporado en las listas de devolver sus artículos uno por uno, los diccionarios de devolución de las llaves , uno por uno, los archivos de retorno de las líneas , uno por uno, etc. Y generadores de cambio... así que es donde yield viene en:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

En lugar de yield declaraciones, si usted tenía tres return declaraciones en f123() sólo la primera sería ejecutar, y la función de salida. Pero f123() no es ordinaria de la función. Cuando f123() se llama, no devolver cualquiera de los valores en el rendimiento de las declaraciones! Devuelve un objeto de generador. También, la función no realmente salir - entra en un estado de suspensión. Cuando la for bucle trata de un bucle en el generador de objeto, la función se reanuda a partir de su estado de suspensión, se prolongará hasta la próxima yield y declaración de rendimientos que como el elemento siguiente. Esto ocurre hasta que sale de la función, momento en el que el generador eleva StopIteration, y el bucle termina.

De modo que el generador de objetos, es como una especie de adaptador en uno de los extremos presenta el protocolo de iteración, exponiendo __iter__() y next() métodos para mantener la for bucle feliz. En el otro extremo, sin embargo, se ejecuta la función sólo lo suficiente para obtener el siguiente valor de ella, y lo pone de nuevo en el modo suspendido.

¿Por Qué Utilizar Generadores?

Por lo general, usted puede escribir código que no use generadores, pero implementa la misma lógica. Una opción es utilizar el temporal de la lista de 'truco' que he mencionado antes. Que no va a funcionar en todos los casos, por ejemplo. si usted tiene los bucles infinitos, o puede hacer uso ineficiente de la memoria cuando usted tiene una lista muy larga. El otro enfoque es el de implementar un nuevo iterable clase SomethingIter que mantiene el estado en el que los miembros de instancia y realiza el siguiente paso lógico en next() (o __next__() en Python 3) el método. Dependiendo de la lógica, el código dentro de la next() método puede acabar pareciendo muy complejo y propenso a errores. Aquí generadores proporcionan una solución limpia y fácil.

182voto

Jason Baker Puntos 56682

Piénsalo de esta manera:

Un iterador es sólo una fantasía término que suena para un objeto que tiene un método next (). Para un rendimiento-ed función termina siendo algo como esto:

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 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 obtener más conocimiento en cuanto a lo que está sucediendo detrás de las escenas, el bucle for puede ser reescrito para esta:

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

Hace más de sentido o simplemente confundir más? :)

EDIT: debo señalar que esta ES una simplificación para propósitos ilustrativos. :)

EDIT 2: se Olvidó de lanzar la excepción StopIteration

157voto

ninjagecko Puntos 25709

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

  1. Si el compilador detecta el yield de palabras clave en cualquier lugar dentro de una función, la función no devuelve a través de la return declaración. En su lugar, inmediatamente devuelve un perezoso "lista de espera" objeto llamado un generador
  2. Un generador es iterable. ¿Qué es un iterable? Es algo como un list o set o range o dict-vista, con un protocolo integrado para la visita de cada elemento en un cierto orden.

En pocas palabras: un generador es un perezoso, gradualmente-lista de espera, y yield declaraciones de permitir el uso de la notación de función para el programa de la lista de valores el generador debe incremental de escupir.

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

Ejemplo

Vamos a definir una función makeRange que es tal como Python, range. Llamando makeRange(n) DEVUELVE UN 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 forzar a que el generador de regresar inmediatamente a sus valores pendientes, que puede pasar en list() (tal y como podría cualquier iterable):

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

La comparación de ejemplo para "acaba de regresar de una lista"

El ejemplo anterior puede ser considerado simplemente como la creación de una lista que se anexa y de devolución:

# 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
    return TO_RETURN             #>

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

Hay una diferencia importante; consulte la última sección.


Cómo podría utilizar generadores de

Un iterable es la última parte de una lista de comprensión, y todos los generadores están iterable, por lo que muchas veces se utiliza así:

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

Para conseguir una mejor sensación para los generadores, usted puede jugar con los itertools módulo (asegúrese de usar chain.from_iterable en lugar de chain cuando esté justificado). Por ejemplo, usted puede incluso utilizar los generadores de implementar infinitamente largos y perezosos listas como itertools.count(). Se podía implementar su propia def enumerate(iterable): zip(count(), iterable), o, alternativamente, hacerlo con el yield de palabras clave en un bucle while.

Por favor nota: los generadores en realidad puede ser utilizado para muchas cosas más, tales como la implementación de corrutinas o no-determinista de programación o de otras cosas elegantes. Sin embargo, el "perezoso listas de" punto de vista que expongo aquí es el uso más común que se encuentra.


Detrás de las escenas

Esto es como el "Python iteración de protocolo". Es decir, lo que está sucediendo cuando usted haga list(makeRange(5)). Esto es lo que yo describo anteriormente como "perezoso, incremental lista".

>>> 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 función incorporada next() sólo las llamadas de los objetos .next() función, que es una parte de la "iteración protocolo" y se encuentra en todos los iteradores. Usted puede utilizar manualmente el next() de función (y otras partes de la iteración protocolo) para la implantación de cosas de lujo, generalmente en detrimento de la legibilidad, para tratar de evitar que...


Minucias

Normalmente, la mayoría de la gente no se interesa por los siguientes distinciones y probablemente querrás dejar de leer aquí.

En Python-hablar, un iterable es cualquier objeto que "entiende el concepto de una for-loop" como una lista [1,2,3], y un iterador es una instancia específica de la solicitud de bucle como [1,2,3].__iter__(). Un generador es exactamente el mismo que el de cualquier iterador, excepto por la forma en que fue escrito (con la sintaxis de la función).

Cuando usted solicita un iterador de una lista, se crea un nuevo iterador. Sin embargo, cuando usted solicita un iterador de un iterador (que rara vez lo hacen), solo le da a usted una copia de sí mismo.

Así, en el improbable caso de que usted no son capaces de hacer algo como esto...

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

... a continuación, recordar que un generador es un iterador; es decir, es de un solo uso. Si desea volver a utilizarlo, usted debe llamar a myRange(...) de nuevo. Aquellos que absolutamente necesidad de clonar un generador (por ejemplo, que están haciendo un hackish metaprogramación) puede utilizar itertools.tee si es absolutamente necesario, ya que el copiable iterador Python PEP normas de la propuesta ha sido aplazado.

122voto

Robert Rossney Puntos 43767

Me siento como puedo publicar un enlace a esta presentación todos los días: David M. Beazly del Generador de Trucos para Sistemas de Programadores. Si eres un programador Python y no estás muy familiarizado con los generadores, usted debe leer esto. Es una explicación muy clara de lo que los generadores son, cómo funcionan, cuál es el rendimiento de la instrucción, y contesta a la pregunta "¿de verdad quieres complicarte con este oscuro característica del lenguaje?"

ALERTA DE SPOILER. La respuesta es: Sí. Sí, usted.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X