¿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.
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)
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.
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.
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).
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.
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()
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.
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.
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.
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.
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.
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.
- Particularmente interesante es que ahora es posible actualizar la variable de rendimiento desde fuera de la función del generador, lo que hace posible crear corrutinas dinámicas e entrelazadas con relativamente poco esfuerzo.
- Véase también PEP 342: Corrutinas vía Generadores mejorados para más información.
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:
- El cliente solicita una url proxy desde el servidor
- El servidor comienza a cargar la url de destino
- El servidor devuelve los resultados al cliente tan pronto como los recibe
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.)
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.
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)
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