¿Para qué se pueden usar las funciones del generador Python?


Estoy empezando a aprender Python y me he encontrado con funciones generadoras, aquellas que tienen una declaración de rendimiento en ellas. Quiero saber qué tipos de problemas que estas funciones son realmente buenos para resolver.

Author: Uli Köhler, 2008-09-19

16 answers

Los generadores te dan una evaluación perezosa. Los utilizas iterando sobre ellos, ya sea explícitamente con 'for' o implícitamente pasándolo a cualquier función o construcción que itere. Puede pensar que los generadores devuelven varios elementos, como si devolvieran una lista, pero en lugar de devolverlos todos a la vez, los devuelven uno por uno, y la función del generador se detiene hasta que se solicita el siguiente elemento.

Los generadores son buenos para calcular grandes conjuntos de resultados (en particular cálculos que involucran bucles) donde no sabes si vas a necesitar todos los resultados, o donde no quieres asignar la memoria para todos los resultados al mismo tiempo. O para situaciones en las que el generador utiliza otro generador , o consume algún otro recurso, y es más conveniente si eso sucedió lo más tarde posible.

Otro uso para generadores (que es realmente el mismo) es reemplazar callbacks con iteración. En algunas situaciones que desea una función para hacer mucho trabajo y de vez en cuando informar a la persona que llama. Tradicionalmente usarías una función de devolución de llamada para esto. Usted pasa esta devolución de llamada a la función de trabajo y periódicamente llamaría a esta devolución de llamada. El enfoque del generador es que la función de trabajo (ahora un generador) no sabe nada sobre la devolución de llamada, y simplemente rinde cada vez que quiere informar algo. La persona que llama, en lugar de escribir una devolución de llamada separada y pasar eso a la función de trabajo, hace todo el trabajo de informes en un poco 'para' gira alrededor del generador.

Por ejemplo, digamos que escribiste un programa de 'búsqueda del sistema de archivos'. Puede realizar la búsqueda en su totalidad, recopilar los resultados y luego mostrarlos uno a la vez. Todos los resultados tendrían que ser recogidos antes de mostrar el primero, y todos los resultados estarían en la memoria al mismo tiempo. O podría mostrar los resultados mientras los encuentra, lo que sería más eficiente en memoria y mucho más amigable con el usuario. Esto último podría hacerse pasando la función de impresión de resultados a la función de búsqueda del sistema de archivos, o podría hacerse simplemente haciendo que la función de búsqueda sea un generador e iterando sobre el resultado.

Si desea ver un ejemplo de los dos últimos enfoques, consulte os.camino.walk () (la antigua función de sistema de archivos-walking con callback) y os.walk () (el nuevo sistema de archivos-walking generator.) Por supuesto, si realmente desea recopilar todos los resultados en una lista, el enfoque del generador es trivial para convertir a la lista grande enfoque:

big_list = list(the_generator)
 222
Author: Thomas Wouters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-08 22:00:24

Una de las razones para usar generator es hacer la solución más clara para algún tipo de soluciones.

El otro es tratar los resultados uno a la vez, evitando construir enormes listas de resultados que procesaría separados de todos modos.

Si tienes una función fibonacci-up-to-n como esta:

# function version
def fibon(n):
    a = b = 1
    result = []
    for i in xrange(n):
        result.append(a)
        a, b = b, a + b
    return result

Puede escribir más fácilmente la función de la siguiente manera:

# generator version
def fibon(n):
    a = b = 1
    for i in xrange(n):
        yield a
        a, b = b, a + b

La función es más clara. Y si utilizas la función así:

for x in fibon(1000000):
    print x,

En este ejemplo, si se usa la versión del generador, la lista completa de 1000000 artículos no se creará en absoluto, solo un valor a la vez. Ese no sería el caso cuando se utilizara la versión de la lista, en la que primero se crearía una lista.

 85
Author: nosklo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 15:09:28

Ver la sección "Motivación" en PEP 255.

Un uso no obvio de generadores es crear funciones interrumpibles, lo que le permite hacer cosas como actualizar la interfaz de usuario o ejecutar varios trabajos "simultáneamente" (intercalados, en realidad) sin usar subprocesos.

 37
Author: Nickolay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 15:07:13

Encuentro esta explicación que aclara mi duda. Porque hay una posibilidad de que la persona que no sabe Generators tampoco sabe acerca de yield

Return

La instrucción return es donde se destruyen todas las variables locales y el valor resultante se devuelve (devuelve) al llamador. Si la misma función se llama algún tiempo después, la función obtendrá un nuevo conjunto de variables.

Yield

Pero ¿qué pasa si el local ¿las variables no se desechan cuando salimos de una función? Esto implica que podemos resume the function donde lo dejamos. Aquí es donde se introduce el concepto de generators y la instrucción yield se reanuda donde lo dejó function.

  def generate_integers(N):
    for i in xrange(N):
    yield i

    In [1]: gen = generate_integers(3)
    In [2]: gen
    <generator object at 0x8117f90>
    In [3]: gen.next()
    0
    In [4]: gen.next()
    1
    In [5]: gen.next()

Así que esa es la diferencia entre las sentencias return y yield en Python.

Declaración de rendimiento es lo que hace que una función una función de generador.

Así que los generadores son una herramienta simple y poderosa para crear iteradores. Son escritas como funciones regulares, pero usan la instrucción yield cuando quieren devolver datos. Cada vez que se llama next (), el generador reanuda donde lo dejó (recuerda todos los valores de datos y qué instrucción se ejecutó por última vez).

 33
Author: Mirage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-20 12:03:27

Buffering. Cuando es eficiente obtener datos en trozos grandes, pero procesarlos en trozos pequeños, entonces un generador podría ayudar:

def bufferedFetch():
  while True:
     buffer = getBigChunkOfData()
     # insert some code to break on 'end of data'
     for i in buffer:    
          yield i

Lo anterior le permite separar fácilmente el almacenamiento en búfer del procesamiento. La función del consumidor ahora puede obtener los valores uno por uno sin preocuparse por el almacenamiento en búfer.

 26
Author: Rafał Dowgird,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 15:14:10

Ejemplo del Mundo Real

Digamos que tienes 100 millones de dominios en tu tabla MySQL, y te gustaría actualizar Alexa rank para cada dominio.

Lo primero que necesita es seleccionar sus nombres de dominio de la base de datos.

Digamos que el nombre de la tabla es domains y el nombre de la columna es domain.

Si usas SELECT domain FROM domains va a devolver 100 millones de filas que va a consumir mucha memoria. Por lo que su servidor podría bloquearse.

Así que decidió ejecutar el programa en lotes. Digamos que nuestro tamaño de lote es 1000.

En nuestro primer lote consultaremos las primeras 1000 filas, comprobaremos el rango de Alexa para cada dominio y actualizaremos la fila de la base de datos.

En nuestro segundo lote trabajaremos en las próximas 1000 filas. En nuestro tercer lote será de 2001 a 3000, y así sucesivamente.

Ahora necesitamos una función generadora que genere nuestros lotes.

Aquí está nuestra función de generador:

def ResultGenerator(cursor, batchsize=1000):
    while True:
        results = cursor.fetchmany(batchsize)
        if not results:
            break
        for result in results:
            yield result

Como puedes ver, nuestra función mantiene yield el resultado. Si utilizas la palabra clave return en lugar de yield, entonces toda la función finalizará una vez que alcance return.

return - returns only once
yield - returns multiple times

Si una función utiliza la palabra clave yield entonces es un generador.

Ahora puedes iterar así:

db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
    doSomethingWith(result)
db.close()
 26
Author: Giri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-20 11:16:57

He encontrado que los generadores son muy útiles en la limpieza de su código y al darle una forma muy única de encapsular y modularizar el código. En una situación en la que necesita algo para escupir constantemente valores basados en su propio procesamiento interno y cuando ese algo necesita ser llamado desde cualquier lugar de su código (y no solo dentro de un bucle o un bloque, por ejemplo), los generadores son la característica para usar.

Un ejemplo abstracto sería un generador de números de Fibonacci que no vive dentro de un bucle y cuando se llama desde cualquier lugar siempre devolverá el siguiente número en la secuencia:

def fib():
    first = 0
    second = 1
    yield first
    yield second

    while 1:
        next = first + second
        yield next
        first = second
        second = next

fibgen1 = fib()
fibgen2 = fib()

Ahora tienes dos objetos generador de números Fibonacci a los que puedes llamar desde cualquier parte de tu código y siempre devolverán números Fibonacci cada vez más grandes en secuencia de la siguiente manera:

>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5

Lo bueno de los generadores es que encapsulan el estado sin tener que pasar por los aros de la creación de objetos. Una forma de pensar en ellos es como "funciones" que recuerdan su estado interno.

Obtuve el ejemplo de Fibonacci de Generadores de Python - ¿Qué son? y con un poco de imaginación, se puede llegar a un montón de otras situaciones donde los generadores hacen una gran alternativa a for bucles y otras construcciones de iteración tradicionales.

 20
Author: Andz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-20 11:58:31

La explicación simple: Considere una declaración for

for item in iterable:
   do_stuff()

Muchas veces, todos los elementos de iterable no necesitan estar allí desde el principio, pero se pueden generar sobre la marcha según se requieran. Esto puede ser mucho más eficiente en ambos

  • espacio (nunca es necesario almacenar todos los elementos simultáneamente) y
  • tiempo (la iteración puede terminar antes de que se necesiten todos los elementos).

Otras veces, ni siquiera sabes todos los elementos de antemano. Por ejemplo:

for command in user_input():
   do_stuff_with(command)

No tienes forma de conocer todos los comandos del usuario de antemano, pero puedes usar un bucle agradable como este si tienes un generador que te da comandos:

def user_input():
    while True:
        wait_for_command()
        cmd = get_command()
        yield cmd

Con los generadores también puede tener iteración sobre secuencias infinitas, lo que, por supuesto, no es posible cuando se itera sobre contenedores.

 18
Author: dF.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 15:15:03

Mis usos favoritos son las operaciones "filtrar" y "reducir".

Digamos que estamos leyendo un archivo, y solo queremos las líneas que comienzan con "##".

def filter2sharps( aSequence ):
    for l in aSequence:
        if l.startswith("##"):
            yield l

Entonces podemos usar la función del generador en un bucle apropiado

source= file( ... )
for line in filter2sharps( source.readlines() ):
    print line
source.close()

El ejemplo reducir es similar. Digamos que tenemos un archivo donde necesitamos localizar bloques de líneas <Location>...</Location>. [No etiquetas HTML,sino líneas que parecen etiquetas.]

def reduceLocation( aSequence ):
    keep= False
    block= None
    for line in aSequence:
        if line.startswith("</Location"):
            block.append( line )
            yield block
            block= None
            keep= False
        elif line.startsWith("<Location"):
            block= [ line ]
            keep= True
        elif keep:
            block.append( line )
        else:
            pass
    if block is not None:
        yield block # A partial block, icky

Una vez más, podemos utilizar este generador en un adecuado para bucle.

source = file( ... )
for b in reduceLocation( source.readlines() ):
    print b
source.close()

La idea es que una función generadora nos permite filtrar o reducir una secuencia, produciendo otra secuencia un valor a la vez.

 12
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 15:13:16

Un ejemplo práctico donde se podría hacer uso de un generador es si tiene algún tipo de forma y desea iterar sobre sus esquinas, bordes o lo que sea. Para mi propio proyecto (código fuente aquí) tenía un rectángulo:

class Rect():

    def __init__(self, x, y, width, height):
        self.l_top  = (x, y)
        self.r_top  = (x+width, y)
        self.r_bot  = (x+width, y+height)
        self.l_bot  = (x, y+height)

    def __iter__(self):
        yield self.l_top
        yield self.r_top
        yield self.r_bot
        yield self.l_bot

Ahora puedo crear un rectángulo y un bucle sobre sus esquinas:

myrect=Rect(50, 50, 100, 100)
for corner in myrect:
    print(corner)

En lugar de __iter__ podrías tener un método iter_corners y llamarlo con for corner in myrect.iter_corners(). Es simplemente más elegante usar __iter__ ya que entonces podemos usar el nombre de la instancia de la clase directamente en el for expresión.

 8
Author: Pithikos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-27 23:13:38

Básicamente evitando las funciones de devolución de llamada cuando se itera sobre la entrada manteniendo el estado.

Ver aquíy aquí para una visión general de lo que se puede hacer usando generadores.

 6
Author: MvdD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 15:09:26

Algunas buenas respuestas aquí, sin embargo, también recomendaría una lectura completa del tutorial de Programación Funcional de Python que ayuda a explicar algunos de los casos de uso más potentes de los generadores.

 4
Author: shongololo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-20 11:54:11

Uso generadores cuando nuestro servidor web actúa como proxy:

  1. El cliente solicita una url proxy desde el servidor
  2. El servidor comienza a cargar la url de destino
  3. El servidor devuelve los resultados al cliente tan pronto como los recibe
 2
Author: Brian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 15:17:51

Dado que el método de envío de un generador no se ha mencionado, aquí hay un ejemplo:

def test():
    for i in xrange(5):
        val = yield
        print(val)

t = test()

# Proceed to 'yield' statement
next(t)

# Send value to yield
t.send(1)
t.send('2')
t.send([3])

Muestra la posibilidad de enviar un valor a un generador en ejecución. Un curso más avanzado sobre generadores en el video a continuación (incluyendo yield de explinación, generadores para procesamiento paralelo, escapar del límite de recursión, etc.)

David Beazley sobre generadores en PyCon 2014

 2
Author: John Damen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-20 12:06:32

Montones de cosas. En cualquier momento que desee generar una secuencia de elementos, pero no quiere tener que 'materializarlos' todos en una lista a la vez. Por ejemplo, podría tener un generador simple que devuelve números primos:

def primes():
    primes_found = set()
    primes_found.add(2)
    yield 2
    for i in itertools.count(1):
        candidate = i * 2 + 1
        if not all(candidate % prime for prime in primes_found):
            primes_found.add(candidate)
            yield candidate

Entonces podría usar eso para generar los productos de los primos posteriores:

def prime_products():
    primeiter = primes()
    prev = primeiter.next()
    for prime in primeiter:
        yield prime * prev
        prev = prime

Estos son ejemplos bastante triviales, pero se puede ver cómo puede ser útil para el procesamiento de grandes (potencialmente infinito!) conjuntos de datos sin generarlos de antemano, que es sólo uno de los usos más obvios.

 1
Author: Nick Johnson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 18:03:49

También es bueno para imprimir los números primos hasta n:

def genprime(n=10):
    for num in range(3, n+1):
        for factor in range(2, num):
            if num%factor == 0:
                break
        else:
            yield(num)

for prime_num in genprime(100):
    print(prime_num)
 0
Author: tryptofan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-22 14:06:40