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:
- Usted no necesita leer los valores de dos veces.
- 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.