Pregunta ¿Qué significa la palabra clave "rendimiento"?


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

Por ejemplo, estoy tratando de entender este 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  

Y esta es la persona que llama:

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é pasa cuando el método _get_child_candidates ¿se llama? ¿Se devuelve una lista? Un solo elemento? Se llama nuevamente? ¿Cuándo se detendrán las llamadas subsiguientes?


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


8311
2017-10-23 22:21


origen


Respuestas:


Para entender que yield lo hace, debes entender qué generadores son. Y antes de que lleguen los generadores iterables.

Iterables

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

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

mylist es un iterable. Cuando usas una lista de comprensión, creas una lista, y así iterable:

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

Todo lo que puedes usar "for... in..."on" es un iterable; lists, strings, archivos ...

Estos iterables son útiles porque puede leerlos tanto como lo desee, pero almacena todos los valores en la memoria y esto no siempre es lo que quiere cuando tiene muchos valores.

Generadores

Los generadores son iteradores, un tipo de iterable solo puedes iterar más de una vez. Los generadores no almacenan todos los valores en la memoria, generan los valores sobre la marcha:

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

Es lo mismo, excepto que usaste () en lugar de []. Pero tu no poder realizar for i in mygenerator una segunda vez ya que los generadores solo pueden usarse una vez: calculan 0, luego se olvidan de él y calculan 1, y terminan calculando 4, uno por uno.

rendimiento

yield es una palabra clave que se usa 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

Aquí es un ejemplo inútil, pero es útil cuando sabes que tu función devolverá un gran conjunto de valores que solo necesitarás leer una vez.

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

Luego, su código se ejecutará cada vez que for usa el generador

Ahora la parte difícil:

La primera vez que for llama al objeto generador creado desde su función, ejecutará el código en su función desde el principio hasta que llegue yield, luego devolverá el primer valor del ciclo. Luego, cada otra llamada ejecutará el ciclo que haya escrito en la función una vez más, y devolverá el siguiente valor, hasta que no haya ningún valor que devolver.

El generador se considera vacío una vez que se ejecuta la función, pero no golpea yield nunca más. Puede ser porque el ciclo ha llegado a su fin, o porque no satisface una "if/else" nunca más.


Tu 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

Llamador:

# 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 itera en una lista, pero la lista se expande mientras se itera el ciclo :-) Es una forma concisa de recorrer todos estos datos anidados, incluso si es un poco peligroso ya que puede terminar con un ciclo infinito. En este caso, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) agota todos los valores del generador, pero while sigue creando nuevos objetos de generador que producirán valores diferentes a los anteriores ya que no se aplica en el mismo nodo.

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

Por lo general, le pasamos una lista:

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

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

  1. No necesita leer los valores dos veces.
  2. Puede tener muchos hijos y no quiere que todos estén almacenados en la memoria.

Y funciona porque a Python no le importa si el argumento de un método es una lista o no. ¡Python espera iterables para que funcione con cadenas, listas, tuplas y generadores! Esto se llama tipa de pato y es una de las razones por las que Python es tan genial. Pero esta es otra historia, por otra pregunta ...

Puede detenerse aquí, o leer un poco para ver un uso avanzado de un generador:

Controlar el agotamiento de un generador

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

Nota: Para Python 3, useprint(corner_street_atm.__next__()) o print(next(corner_street_atm))

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

Itertools, tu mejor amigo

El módulo itertools contiene funciones especiales para manipular iterables. ¿Alguna vez has querido duplicar un generador? ¿Cadena de dos generadores? ¿Agrupar valores en una lista anidada con un solo liner? Map / Zip sin crear otra lista?

Entonces solo import itertools.

¿Un ejemplo? Veamos los posibles pedidos de llegada para una carrera de cuatro 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 internos de iteración

La iteración es un proceso que implica iterables (implementando __iter__() método) e iteradores (implementando el __next__() método). Los Iterables son cualquier objeto del que puede obtener un iterador. Los iteradores son objetos que te permiten iterar en iterables.

Hay más sobre esto en este artículo sobre cómo for bucles funcionan.


12172
2017-10-23 22:48



atajo a Grokking  yield

Cuando ves una función con yield declaraciones, aplique este truco fácil para entender lo que sucederá:

  1. Insertar una linea result = [] al comienzo de la función.
  2. Reemplazar cada yield expr con result.append(expr).
  3. Insertar una linea return result en la parte inferior de la función.
  4. Yay, no más yield declaraciones! Lee y descubre el código.
  5. Compare la función con la definición original.

Este truco puede darte una idea de la lógica detrás de la función, pero lo que realmente sucede con 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 en la memoria y más rápido también. En otros casos, este truco te atrapará en un ciclo infinito, a pesar de que la función original funciona bien. Sigue leyendo para saber más ...

No confunda sus Iterables, Iteradores y Generadores

Primero el protocolo de iterador - cuando escribes

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

Python realiza los dos pasos siguientes:

  1. Obtiene un iterador para mylist:

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

    [Este es el paso del que la mayoría de la gente se olvida decírtelo]

  2. Utiliza el iterador para recorrer los elementos:

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

La verdad es que Python realiza los dos pasos anteriores cada vez que quiere pasar por encima el contenido de un objeto, por lo que podría ser un ciclo for, pero también podría ser como un código otherlist.extend(mylist) (dónde otherlist es una lista de Python).

aquí mylist es un iterable porque implementa el protocolo de iterador. En una clase definida por el usuario, puede implementar __iter__() método para hacer iterables las instancias de su clase. Este método debería devolver un iterador. Un iterador es un objeto con un next() método. Es posible implementar ambos __iter__() y next() en la misma clase, y tienen __iter__() regreso self. Esto funcionará para casos simples, pero no cuando desee que dos iteradores recorran el mismo objeto al mismo tiempo.

Entonces ese es el protocolo iterador, muchos objetos implementan este protocolo:

  1. Listas integradas, diccionarios, tuplas, conjuntos, archivos.
  2. Clases definidas por el usuario que implementan __iter__().
  3. Generadores.

Tenga en cuenta que for loop no sabe con qué tipo de objeto está tratando, simplemente sigue el protocolo del iterador y se complace en obtener un elemento tras otro cuando llama next(). Las listas incorporadas devuelven sus artículos uno por uno, los diccionarios devuelven el llaves uno por uno, los archivos devuelven líneas uno a uno, etc. Y los generadores vuelven ... bueno, ahí 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 tuviera tres return declaraciones en f123() solo el primero se ejecutaría, y la función saldría. Pero f123() no es una función ordinaria. Cuando f123() se llama, se no devolver cualquiera de los valores en las declaraciones de rendimiento! Devuelve un objeto generador. Además, la función realmente no sale: entra en estado suspendido. Cuando el for el bucle intenta recorrer el objeto del generador, la función se reanuda desde su estado suspendido en la siguiente línea después del yield de donde anteriormente regresó, ejecuta la siguiente línea de código, en este caso una yield declaración, y la devuelve como el siguiente artículo. Esto sucede hasta que la función finaliza, momento en el que el generador aumenta StopIteration, y el bucle sale.

Entonces, el objeto generador es algo así como un adaptador - en un extremo exhibe el protocolo de iterador, exponiendo __iter__() y next() métodos para mantener el for lazo feliz En el otro extremo, sin embargo, ejecuta la función lo suficiente para obtener el siguiente valor y lo pone nuevamente en modo suspendido.

¿Por qué usar generadores?

Usualmente puedes escribir código que no usa generadores pero implementa la misma lógica. Una opción es usar la lista temporal 'truco' que mencioné antes. Eso no funcionará en todos los casos, por ej. si tiene bucles infinitos, o puede hacer un uso ineficiente de la memoria cuando tiene una lista realmente larga. El otro enfoque es implementar una nueva clase iterable SomethingIter que mantiene el estado en los miembros de la instancia y realiza el siguiente paso lógico en su next() (o __next__() en Python 3) método. Dependiendo de la lógica, el código dentro del next() método puede terminar pareciendo muy complejo y propenso a errores. Aquí los generadores proporcionan una solución limpia y fácil.


1635
2017-10-25 21:22



Piénsalo de esta manera:

Un iterador es simplemente un término que suena elegante para un objeto que tiene un método next (). Entonces, una función de rendimiento 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 hace 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 información sobre lo que está sucediendo detrás de escena, el for loop puede ser reescrito a esto:

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

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

Debo señalar que esto es una simplificación excesiva con fines ilustrativos. :)


396
2017-10-23 22:28



los yield la palabra clave se reduce a dos hechos simples:

  1. Si el compilador detecta el yield palabra clave en cualquier sitio dentro de una función, esa función ya no regresa a través del return declaración. En lugar, eso inmediatamente devuelve un objeto perezoso "lista pendiente" llamado generador
  2. Un generador es iterable. Que es un iterable? Es como un list o set o range o dict-view, con un protocolo incorporado para visitar cada elemento en un cierto orden.

En una palabra: un generador es una lista floja, progresivamente pendientey yield las declaraciones le permiten usar la notación de función para programar los valores de la lista el generador debería escupir incrementalmente.

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 eso es como el de Python range. Vocación 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 al generador a devolver inmediatamente sus valores pendientes, puede pasarlo a list() (al igual que podría cualquier iterable):

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

Comparando el ejemplo con "solo devolver una lista"

El ejemplo anterior puede considerarse simplemente como crear una lista a la que anexas y regresas:

# 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 gran diferencia; ver la última sección.


Cómo puedes usar generadores

Un iterable es la última parte de una lista de comprensión, y todos los generadores son iterables, por lo que a menudo se usan de la siguiente manera:

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

Para tener una mejor idea de los generadores, puede jugar con el itertools módulo (asegúrese de usar chain.from_iterable más bien que chain cuando esté justificado). Por ejemplo, incluso podría usar generadores para implementar listas perezosas infinitamente largas como itertools.count(). Usted podría implementar su propio def enumerate(iterable): zip(count(), iterable), o alternativamente, hazlo con el yieldpalabra clave en un while-loop.

Tenga en cuenta: los generadores se pueden usar para muchas cosas más, como implementando corutinas o programación no determinista u otras cosas elegantes. Sin embargo, el punto de vista de "listas diferidas" que presento aquí es el uso más común que encontrarás.


Entre bastidores

Así es como funciona el "protocolo de iteración de Python". Es decir, qué está sucediendo cuando lo haces list(makeRange(5)). Esto es lo que describo anteriormente como una "lista incremental y 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 función incorporada next() simplemente llama a los objetos .next() función, que es una parte del "protocolo de iteración" y se encuentra en todos los iteradores. Puedes usar el next() función (y otras partes del protocolo de iteración) para implementar cosas sofisticadas, generalmente a expensas de la legibilidad, así que trate de evitar hacer eso ...


Minucias

Normalmente, a la mayoría de las personas no les importarían las siguientes distinciones y probablemente quieran dejar de leer aquí.

En Python-speak, un iterable es cualquier objeto que "entiende el concepto de un bucle for" como una lista [1,2,3], y un iterador es una instancia específica de lo solicitado for-loop como [1,2,3].__iter__(). UN 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 una lista, crea un nuevo iterador. Sin embargo, cuando solicita un iterador de un iterador (que rara vez haría), simplemente le da una copia de sí mismo.

Por lo tanto, en el improbable caso de que no puedas hacer algo como esto ...

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

... entonces recuerda que un generador es un iterador; es decir, es de uso único. Si quiere reutilizarlo, debe llamar myRange(...) de nuevo. Si necesita usar el resultado dos veces, convierta el resultado a una lista y guárdelo en una variable x = list(myRange(5)). Aquellos que necesitan absolutamente clonar un generador (por ejemplo, quienes están haciendo una metaprogramación terriblemente hackosa) pueden usar itertools.tee si es absolutamente necesario, ya que el iterador copiable Python ENERGÍA la propuesta de estándares ha sido diferida.


346
2018-06-19 06:33



Lo que hace el yield palabra clave do en Python?

Resumen de respuesta / Resumen

  • Una función con yieldcuando se llama devuelve un Generador.
  • Los generadores son iteradores porque implementan el protocolo de iterador, para que pueda iterar sobre ellos.
  • Un generador también puede ser información enviada, haciéndolo conceptualmente un corotine.
  • En Python 3, puedes delegar de un generador a otro en ambas direcciones con yield from.
  • (El apéndice critica un par de respuestas, incluida la más importante, y discute el uso de return en un generador)

Generadores:

yield es solo legal dentro de una definición de función, y la inclusión de yield en una definición de función hace que devuelva un generador.

La idea de los generadores proviene de otros lenguajes (ver la nota a pie de página 1) con diferentes implementaciones. En Python's Generators, la ejecución del código es congelado en el punto del rendimiento. Cuando se llama al generador (los métodos se describen a continuación), la ejecución se reanuda y luego se congela con el siguiente rendimiento.

yield proporciona un manera fácil de implementando el protocolo de iterador, definido por los dos métodos siguientes: __iter__ y next (Python 2) o __next__ (Python 3). Ambos de esos métodos hacer que un objeto sea un iterador que podría escribir, verifique con Iterator Base abstracta Clase del 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.

El tipo de generador es un subtipo de iterador:

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

Y si es necesario, podemos hacer una verificación de tipo así:

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

Una característica de un Iterator  es que una vez agotado, no puedes reutilizarlo o restablecerlo:

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

Tendrá que hacer otra si desea volver a utilizar su funcionalidad (ver nota al pie 2):

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

Uno puede producir datos programáticamente, por ejemplo:

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

El generador simple anterior también es equivalente al siguiente: a partir de Python 3.3 (y no disponible en Python 2), puede usar yield from:

def func(an_iterable):
    yield from an_iterable

Sin embargo, yield from también permite la delegación a los subgeneradores, que se explicará en la siguiente sección sobre la delegación cooperativa con subcoroutinas.

Corutinas:

yield forma una expresión que permite que los datos se envíen al generador (ver nota 3)

Aquí hay un ejemplo, tome nota de la received variable, que apuntará a los datos que se envían al generador:

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)

Primero, debemos poner en cola el generador con la función incorporada, next. Va a llamar al apropiado next o __next__ método, dependiendo de la versión de Python estás usando:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

Y ahora podemos enviar datos al generador. (Enviando None es lo mismo que llamar next.):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Delegación Cooperativa a la Subcompetencia con yield from

Ahora, recuerda eso yield from está disponible en Python 3. Esto nos permite delegar corutinas a una subcoroutine:

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

Y ahora podemos delegar la funcionalidad a un subgenerador y se puede usar por un generador como el anterior:

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

Puede leer más sobre la semántica precisa de yield from en PEP 380.

Otros métodos: cerrar y lanzar

los close Aumenta el método GeneratorExit en el punto de la función la ejecución estaba congelada. Esto también será llamado por __del__ vos tambien puede poner cualquier código de limpieza donde maneje el GeneratorExit:

>>> my_account.close()

También puedes lanzar una excepción que se puede manejar en el generador o propagado de vuelta al usuario:

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

Conclusión

Creo que he cubierto todos los aspectos de la siguiente pregunta:

Lo que hace el yield palabra clave do en Python?

Resulta que yield hace mucho Estoy seguro de que podría agregar aún más ejemplos completos de esto. Si quieres más o tienes alguna crítica constructiva, házmelo saber comentando abajo.


Apéndice:

Crítica de la parte superior / respuesta aceptada **

  • Se confunde en lo que hace una iterable, simplemente usando una lista como ejemplo. Ver mis referencias arriba, pero en resumen: un iterable tiene una __iter__ método que devuelve iterador. Un iterador provee un .next (Python 2 o .__next__ (Python 3) método, que es llamado implícitamente por forbucles hasta que sube StopIteration, y una vez que lo haga, continuará haciéndolo.
  • Luego usa una expresión generadora para describir qué es un generador. Dado que un generador es simplemente una forma conveniente de crear un iterador, solo confunde el asunto, y todavía no hemos llegado al yield parte.
  • En Controlar el agotamiento de un generador él llama al .next método, cuando en su lugar debería usar la función incorporada, next. Sería una capa apropiada de indirección, porque su código no funciona en Python 3.
  • Itertools? Esto no era relevante para lo que yield lo hace en absoluto.
  • No hay discusión de los métodos que yield proporciona junto con la nueva funcionalidad yield from en Python 3. La respuesta superior / aceptada es una respuesta muy incompleta.

Crítica de la respuesta que sugiere yield en una expresión o comprensión generadora.

La gramática actualmente permite cualquier expresión en una lista de comprensión.

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

Dado que el rendimiento es una expresión, algunos lo han promocionado como interesante para usarlo en las comprensiones o en la expresión del generador, a pesar de no citar ningún caso de uso particularmente bueno.

Los desarrolladores principales de CPython son discutiendo desaprobar su asignación. Aquí hay una publicación relevante de la lista de correo:

El 30 de enero de 2017 a las 19:05, Brett Cannon escribió:

El domingo 29 de enero de 2017 a las 16:39 Craig Rodrigues escribió:

Estoy bien con cualquiera de los enfoques. Dejando las cosas como están en Python 3       no es bueno, en mi humilde opinión.

Mi voto es ser un SyntaxError ya que no está obteniendo lo que espera de     la sintaxis

Estoy de acuerdo en que es un lugar sensato para que terminemos, como cualquier código   confiar en el comportamiento actual es realmente demasiado inteligente para ser   mantenible

En términos de llegar allí, es probable que deseemos:

  • SintaxisAdvertencia o DeprecaciónAdvertencia en 3.7
  • Advertencia Py3k en 2.7.x
  • SyntaxError en 3.8

Saludos, Nick.

- Nick Coghlan | ncoghlan en gmail.com | Brisbane, Australia

Además, hay un cuestión pendiente (10544) que parece apuntar en la dirección de este Nunca siendo una buena idea (PyPy, una implementación de Python escrita en Python, ya está generando advertencias de sintaxis).

En pocas palabras, hasta que los desarrolladores de CPython nos digan lo contrario: No pongas yield en una expresión o comprensión generadora.

los return declaración en un generador

En Python 2:

En una función de generador, el return declaración no está permitido incluir un expression_list. En ese contexto, un desnudo return indica que el generador está hecho y causará StopIteration ser criado.

Un expression_list es básicamente cualquier cantidad de expresiones separadas por comas; esencialmente, en Python 2, puede detener el generador con return, pero no puedes devolver un valor.

En Python 3:

En una función de generador, el return declaración indica que el generador está hecho y causará StopIteration ser criado. El valor devuelto (si lo hay) se usa como argumento para construir StopIteration y se convierte en el StopIteration.valueatributo.

Notas a pie de página

  1. Los idiomas CLU, Sather e Icon se mencionaron en la propuesta para presentar el concepto de generadores a Python. La idea general es que una función puede mantener el estado interno y el rendimiento intermedio puntos de datos a petición del usuario. Esto prometió ser superior en rendimiento a otros enfoques, incluido el enhebrado de Python, que ni siquiera está disponible en algunos sistemas.

  2.  Esto significa, por ejemplo, que xrange objetos (range en Python 3) no son Iterators, aunque son iterables, porque pueden reutilizarse. Al igual que las listas, su __iter__ los métodos devuelven los objetos del iterador.

  3. yield fue presentado originalmente como una declaración, lo que significa que solo podría aparecer al comienzo de una línea en un bloque de código. Ahora yield crea una expresión de rendimiento. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt  Este cambio fue propuesto para permitir que un usuario envíe datos al generador al igual que uno podría recibirlo. Para enviar datos, uno debe poder asignarlo a algo, y para eso, una declaración simplemente no funcionará.


253
2018-06-25 06:11



yield es como return - Devuelve lo que le diga (como generador). La diferencia es que la próxima vez que llame al generador, la ejecución comienza desde la última llamada al yield declaración. A diferencia del retorno, el marco de la pila no se limpia cuando se produce un rendimiento, sin embargo, el control se transfiere de vuelta a la persona que llama, por lo que su estado se reanudará la próxima vez que se ejecute la función.

En el caso de tu código, la función get_child_candidates actúa como un iterador para que cuando amplíe su lista, agregue un elemento a la vez a la nueva lista.

list.extend llama a un iterador hasta que se agota. En el caso de la muestra de código que publicó, sería mucho más claro devolver una tupla y anexarla a la lista.


230
2017-10-23 22:24



Hay una cosa más que mencionar: una función que rinde en realidad no tiene que terminar. He escrito un código como este:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Entonces puedo usarlo en otro código como este:

for f in fib():
    if some_condition: break
    coolfuncs(f);

Realmente ayuda a simplificar algunos problemas y facilita el trabajo con algunas cosas.


182
2017-10-24 08:44



Para aquellos que prefieren un ejemplo de trabajo mínimo, mediten en este interactivo Pitón sesión:

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

155
2018-01-18 17:25



El rendimiento te da un generador.

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 puede ver, en el primer caso foo contiene toda la lista en la memoria a la vez. No es un gran problema para una lista con 5 elementos, pero ¿y si quieres una lista de 5 millones? No solo es un gran devorador de memoria, también cuesta mucho tiempo construirlo en el momento en que se llama a la función. En el segundo caso, la barra solo te da un generador. Un generador es iterable, lo que significa que puede usarlo en un bucle for, etc., pero solo se puede acceder a cada valor una vez. Todos los valores tampoco se almacenan en la memoria al mismo tiempo; el objeto del generador "recuerda" dónde estaba en el bucle la última vez que lo llamó, de esta manera, si usa un recuento iterable para (digamos) hasta 50 mil millones, no tiene que contar hasta 50 mil millones de una vez y almacenar los 50 mil millones de números para contar. De nuevo, este es un ejemplo bastante artificial, probablemente usaría itertools si realmente quisiera contar hasta 50 mil millones. :)

Este es el caso de uso más simple de generadores. Como dijiste, se puede usar para escribir permutaciones eficientes, usando el rendimiento para subir cosas a través de la pila de llamadas en lugar de usar algún tipo de variable de pila. Los generadores también se pueden usar para cruzar árboles especializados y todo tipo de otras cosas.


133
2018-01-16 06:42



Está devolviendo un generador. No estoy particularmente familiarizado con Python, pero creo que es el mismo tipo de cosa que Bloques iteradores de C # si estás familiarizado con eso

Hay un Artículo de IBM lo cual lo explica razonablemente bien (para Python) por lo que puedo ver.

La idea clave es que el compilador / intérprete / lo que sea haga trucos para que, en lo que respecta a la persona que llama, puedan seguir llamando a next () y seguirán devolviendo valores: como si el método del generador estuviera en pausa. Ahora, obviamente, no se puede "pausar" un método, por lo que el compilador crea una máquina de estado para que recuerde dónde se encuentra actualmente y cómo se ven las variables locales, etc. Esto es mucho más fácil que escribir un iterador usted mismo.


125
2017-10-23 22:26