¿Para qué puedes usar las funciones del generador Python?
213
Estoy empezando a aprender Python y he encontrado funciones generadoras, aquellas que tienen una declaración de rendimiento. Quiero saber qué tipos de problemas son realmente buenas para resolver estas funciones.
Los generadores le dan una evaluación perezosa. Los usa iterando sobre ellos, ya sea explícitamente con 'for' o implícitamente pasándolo a cualquier función o construcción que itera. 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 de 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) en los que no sabe si va a necesitar todos los resultados, o donde no desea asignar la memoria para todos los resultados al mismo tiempo . O para situaciones en las que el generador usa otro generador, o consume algún otro recurso, y es más conveniente si eso sucedió lo más tarde posible.
Otro uso para los generadores (que es realmente el mismo) es reemplazar las devoluciones de llamada con iteración. En algunas situaciones, desea que una función haga mucho trabajo y ocasionalmente informe a la persona que llama. Tradicionalmente usarías una función de devolución de llamada para esto. Pasas esta devolución de llamada a la función de trabajo y periódicamente llamará a esta devolución de llamada. El enfoque del generador es que la función de trabajo (ahora un generador) no sabe nada acerca de la devolución de llamada, y simplemente cede cuando quiere informar algo. La persona que llama, en lugar de escribir una devolución de llamada separada y pasarla a la función de trabajo, realiza todo el trabajo de informes en un pequeño bucle 'for' alrededor del generador.
Por ejemplo, supongamos que escribió un programa de 'búsqueda de 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 recopilarse 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 cuanto a 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, vea os.path.walk () (la antigua función de caminar del sistema de archivos con devolución de llamada) y os.walk () (el nuevo generador de caminar del sistema de archivos). Por supuesto, si Realmente quería recopilar todos los resultados en una lista, el enfoque del generador es trivial para convertir al enfoque de la lista grande:
¿Un generador como el que produce listas de sistemas de archivos realiza acciones en paralelo al código que ejecuta ese generador en un bucle? Idealmente, la computadora ejecutaría el cuerpo del bucle (procesando el último resultado) mientras hacía simultáneamente todo lo que el generador debe hacer para obtener el siguiente valor.
Steven Lu
@StevenLu: a menos que se tome la molestia de lanzar subprocesos manualmente antes yieldy joindespués para obtener el siguiente resultado, no se ejecuta en paralelo (y ningún generador de biblioteca estándar hace esto; lanzar subprocesos en secreto está mal visto). El generador se detiene en cada uno yieldhasta que se solicita el siguiente valor. Si el generador está envolviendo E / S, el sistema operativo podría estar almacenando en caché de forma proactiva los datos del archivo, suponiendo que se solicitará en breve, pero ese es el sistema operativo, Python no está involucrado.
ShadowRanger 01 de
90
Una de las razones para usar el generador es hacer que la solución sea más clara para algún tipo de solución.
El otro es tratar los resultados de uno en uno, evitando crear enormes listas de resultados que de todos modos procesaría separados.
Si tiene una función fibonacci-up-to-n como esta:
# function versiondef 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 esta manera:
# generator versiondef fibon(n):
a = b =1for i in xrange(n):yield a
a, b = b, a + b
La función es más clara. Y si usa la función de esta manera:
for x in fibon(1000000):print x,
en este ejemplo, si usa la versión del generador, no se creará la lista completa de artículos 1000000, solo un valor a la vez. Ese no sería el caso cuando se usa la versión de la lista, donde primero se crearía una lista.
Un uso no obvio de los generadores es la creación de funciones interrumpibles, que le permiten hacer cosas como actualizar la interfaz de usuario o ejecutar varios trabajos "simultáneamente" (intercalados, en realidad) sin utilizar subprocesos.
La sección Motivación es agradable porque tiene un ejemplo específico: "Cuando una función de productor tiene un trabajo lo suficientemente difícil como para mantener el estado entre los valores producidos, la mayoría de los lenguajes de programación no ofrecen una solución agradable y eficiente más allá de agregar una función de devolución de llamada al argumento del productor lista ... Por ejemplo, tokenize.py en la biblioteca estándar adopta este enfoque "
Ben Creasy
38
Encuentro esta explicación que aclara mis dudas. Porque existe la posibilidad de que la persona que no sabe Generatorstampoco sepa sobreyield
Regreso
La declaración de devolución es donde todas las variables locales se destruyen y el valor resultante se devuelve (devuelve) a la persona que llama. Si se llama a la misma función algún tiempo después, la función obtendrá un nuevo conjunto de variables nuevas.
rendimiento
Pero, ¿qué pasa si las variables locales no se desechan cuando salimos de una función? Esto implica que podemos resume the functiondonde lo dejamos. Aquí es donde generatorsse introduce el concepto de y la yielddeclaración se reanuda donde la functiondejó.
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()0In[4]: gen.next()1In[5]: gen.next()
Esa es la diferencia entre returnyyield declaraciones en Python.
La declaración de rendimiento es lo que hace que una función sea una función generadora.
Por lo tanto, los generadores son una herramienta simple y poderosa para crear iteradores. Se escriben como funciones regulares, pero usan la yielddeclaración cada vez que desean devolver datos. Cada vez que se llama a next (), el generador reanuda donde lo dejó (recuerda todos los valores de datos y qué declaración se ejecutó por última vez).
Supongamos que tiene 100 millones de dominios en su tabla MySQL y desea actualizar el rango de Alexa para cada dominio.
Lo primero que necesita es seleccionar sus nombres de dominio de la base de datos.
Digamos que el nombre de su tabla es domainsy el nombre de la columna es domain.
Si lo usa SELECT domain FROM domains, devolverá 100 millones de filas, lo que consumirá mucha memoria. Entonces su servidor podría fallar.
Entonces decidió ejecutar el programa en lotes. Digamos que nuestro tamaño de lote es 1000.
En nuestro primer lote, consultaremos las primeras 1000 filas, verificaremos el rango de Alexa para cada dominio y actualizaremos la fila de la base de datos.
En nuestro segundo lote trabajaremos en las siguientes 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:
defResultGenerator(cursor, batchsize=1000):whileTrue:
results = cursor.fetchmany(batchsize)ifnot results:breakfor result in results:yield result
Como puede ver, nuestra función mantiene yieldlos resultados. Si utilizó la palabra clave en returnlugar de yield, entonces toda la función finalizaría una vez que alcanzara el retorno.
return- returns only once
yield- returns multiple times
Si una función usa 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 inResultGenerator(cursor):
doSomethingWith(result)
db.close()
¡Sería más práctico si el rendimiento pudiera explicarse en términos de programación recursiva / dinámica!
igaurav
27
Buffering. Cuando es eficiente obtener datos en fragmentos grandes, pero procesarlos en fragmentos pequeños, un generador podría ayudar:
def bufferedFetch():whileTrue:
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 de consumidor ahora puede obtener los valores uno por uno sin preocuparse por el almacenamiento en búfer.
Si getBigChuckOfData no es perezoso, entonces no entiendo qué beneficio tiene el rendimiento aquí. ¿Cuál es un caso de uso para esta función?
Sean Geoffrey Pietz
1
Pero el punto es que, IIUC, bufferedFetch está demorando la llamada a getBigChunkOfData. Si getBigChunkOfData ya era vago, entonces bufferedFetch sería inútil. Cada llamada a bufferedFetch () devolverá un elemento de búfer, a pesar de que ya se haya leído un BigChunk. Y no es necesario que cuente explícitamente el siguiente elemento para devolver, porque la mecánica del rendimiento lo hace implícitamente.
hmijail llora a los representantes el
21
He descubierto que los generadores son muy útiles para limpiar su código y al brindarle una forma ú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 parte de su código (y no solo dentro de un bucle o un bloque, por ejemplo), los generadores son la característica para utilizar.
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 =1yield first
yield second
while1:
next = first + second
yield next
first = second
second = next
fibgen1 = fib()
fibgen2 = fib()
Ahora tiene dos objetos generadores de números de Fibonacci a los que puede llamar desde cualquier parte de su código y siempre devolverán números de Fibonacci cada vez más grandes en secuencia de la siguiente manera:
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 ellas es como "funciones" que recuerdan su estado interno.
Obtuve el ejemplo de Fibonacci de Python Generators. ¿Qué son? y con un poco de imaginación, puede encontrar muchas otras situaciones en las que los generadores son una excelente alternativa a los forbucles y otras construcciones de iteración tradicionales.
La explicación simple: considere una fordeclaración
for item in iterable:
do_stuff()
La mayoría de las veces, todos los elementos iterableno necesitan estar allí desde el principio, sino que se pueden generar sobre la marcha según sea necesario. Esto puede ser mucho más eficiente en ambos
espacio (nunca necesita almacenar todos los artículos simultáneamente) y
tiempo (la iteración puede finalizar antes de que se necesiten todos los elementos).
Otras veces, ni siquiera conoce todos los elementos con anticipación. Por ejemplo:
for command in user_input():
do_stuff_with(command)
No tiene forma de conocer todos los comandos del usuario de antemano, pero puede usar un buen ciclo como este si tiene un generador que le entrega los comandos:
... y se podría generar una secuencia infinita al recorrer repetidamente una lista pequeña y volver al principio una vez que se alcanza el final. Lo uso para seleccionar colores en gráficos, o producir vibrantes o hilanderos en el texto.
Mis usos favoritos son las operaciones de "filtro" y "reducción".
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 de generador en un bucle apropiado
source= file(...)for line in filter2sharps( source.readlines()):print line
source.close()
El ejemplo de reducción es similar. Digamos que tenemos un archivo donde necesitamos ubicar bloques de <Location>...</Location>líneas. [No son etiquetas HTML, sino líneas que parecen etiquetas.]
def reduceLocation( aSequence ):
keep=False
block=Nonefor line in aSequence:if line.startswith("</Location"):
block.append( line )yield block
block=None
keep=Falseelif line.startsWith("<Location"):
block=[ line ]
keep=Trueelif keep:
block.append( line )else:passif block isnotNone:yield block # A partial block, icky
Nuevamente, podemos usar este generador en un bucle apropiado para.
source = file(...)for b in reduceLocation( source.readlines()):print b
source.close()
La idea es que una función de generador nos permite filtrar o reducir una secuencia, produciendo otra secuencia de un valor a la vez.
fileobj.readlines()leería todo el archivo en una lista en la memoria, frustrando el propósito de usar generadores. Como los objetos de archivo ya son iterables, puede usarlos for b in your_generator(fileobject):en su lugar. De esa manera, su archivo se leerá una línea a la vez, para evitar leer el archivo completo.
nosklo
reduceLocation es bastante extraño produciendo una lista, ¿por qué no solo ceder cada línea? También filtrar y reducir son componentes incorporados con comportamientos esperados (consulte la ayuda en ipython, etc.), su uso de "reducir" es el mismo que el filtro.
James Antill
Buen punto en las líneas de lectura (). Por lo general, me doy cuenta de que los archivos son iteradores de línea de primera clase durante las pruebas unitarias.
S.Lott
En realidad, la "reducción" combina múltiples líneas individuales en un objeto compuesto. De acuerdo, es una lista, pero sigue siendo una reducción tomada de la fuente.
S.Lott
9
Un ejemplo práctico en el que podría utilizar 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:
Ahora puedo crear un rectángulo y recorrer sus esquinas:
myrect=Rect(50,50,100,100)for corner in myrect:print(corner)
En lugar de __iter__usted podría tener un método iter_cornersy llamarlo con for corner in myrect.iter_corners(). Es más elegante de usar __iter__ya que podemos usar el nombre de instancia de clase directamente en la forexpresión.
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.
Como no se ha mencionado el método de envío de un generador, aquí hay un ejemplo:
def test():for i in xrange(5):
val =yieldprint(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 (que incluye yielddesde la expiración, generadores para procesamiento paralelo, escapar del límite de recursión, etc.)
Montones de cosas. Cada vez que desee generar una secuencia de elementos, pero no desee tener que 'materializarlos' en una lista a la vez. Por ejemplo, podría tener un generador simple que devuelva números primos:
def primes():
primes_found = set()
primes_found.add(2)yield2for i in itertools.count(1):
candidate = i *2+1ifnot all(candidate % prime for prime in primes_found):
primes_found.add(candidate)yield candidate
Luego puede usar eso para generar los productos de 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 puede ver cómo puede ser útil para procesar grandes conjuntos de datos (¡potencialmente infinitos!) Sin generarlos de antemano, que es solo uno de los usos más obvios.
si no hay ninguno (candidato% primo para primo en primes_found) debe ser si todos (candidato% primo para primo en primes_found)
rjmunro
Sí, lo que significa que escribir "si no hay (candidato% primer == 0 para privilegiada en primes_found) El suyo es un poco más ordenado, aunque :)..
Nick Johnson
Supongo que olvidó eliminar el 'no' de si no todos (candidato% prime para prime en primes_found)
Thava
0
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:breakelse:yield(num)for prime_num in genprime(100):print(prime_num)
Respuestas:
Los generadores le dan una evaluación perezosa. Los usa iterando sobre ellos, ya sea explícitamente con 'for' o implícitamente pasándolo a cualquier función o construcción que itera. 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 de 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) en los que no sabe si va a necesitar todos los resultados, o donde no desea asignar la memoria para todos los resultados al mismo tiempo . O para situaciones en las que el generador usa otro generador, o consume algún otro recurso, y es más conveniente si eso sucedió lo más tarde posible.
Otro uso para los generadores (que es realmente el mismo) es reemplazar las devoluciones de llamada con iteración. En algunas situaciones, desea que una función haga mucho trabajo y ocasionalmente informe a la persona que llama. Tradicionalmente usarías una función de devolución de llamada para esto. Pasas esta devolución de llamada a la función de trabajo y periódicamente llamará a esta devolución de llamada. El enfoque del generador es que la función de trabajo (ahora un generador) no sabe nada acerca de la devolución de llamada, y simplemente cede cuando quiere informar algo. La persona que llama, en lugar de escribir una devolución de llamada separada y pasarla a la función de trabajo, realiza todo el trabajo de informes en un pequeño bucle 'for' alrededor del generador.
Por ejemplo, supongamos que escribió un programa de 'búsqueda de 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 recopilarse 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 cuanto a 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, vea os.path.walk () (la antigua función de caminar del sistema de archivos con devolución de llamada) y os.walk () (el nuevo generador de caminar del sistema de archivos). Por supuesto, si Realmente quería recopilar todos los resultados en una lista, el enfoque del generador es trivial para convertir al enfoque de la lista grande:
fuente
yield
yjoin
después para obtener el siguiente resultado, no se ejecuta en paralelo (y ningún generador de biblioteca estándar hace esto; lanzar subprocesos en secreto está mal visto). El generador se detiene en cada unoyield
hasta que se solicita el siguiente valor. Si el generador está envolviendo E / S, el sistema operativo podría estar almacenando en caché de forma proactiva los datos del archivo, suponiendo que se solicitará en breve, pero ese es el sistema operativo, Python no está involucrado.Una de las razones para usar el generador es hacer que la solución sea más clara para algún tipo de solución.
El otro es tratar los resultados de uno en uno, evitando crear enormes listas de resultados que de todos modos procesaría separados.
Si tiene una función fibonacci-up-to-n como esta:
Puede escribir más fácilmente la función de esta manera:
La función es más clara. Y si usa la función de esta manera:
en este ejemplo, si usa la versión del generador, no se creará la lista completa de artículos 1000000, solo un valor a la vez. Ese no sería el caso cuando se usa la versión de la lista, donde primero se crearía una lista.
fuente
list(fibon(5))
Vea la sección "Motivación" en PEP 255 .
Un uso no obvio de los generadores es la creación de funciones interrumpibles, que le permiten hacer cosas como actualizar la interfaz de usuario o ejecutar varios trabajos "simultáneamente" (intercalados, en realidad) sin utilizar subprocesos.
fuente
Encuentro esta explicación que aclara mis dudas. Porque existe la posibilidad de que la persona que no sabe
Generators
tampoco sepa sobreyield
Regreso
La declaración de devolución es donde todas las variables locales se destruyen y el valor resultante se devuelve (devuelve) a la persona que llama. Si se llama a la misma función algún tiempo después, la función obtendrá un nuevo conjunto de variables nuevas.
rendimiento
Pero, ¿qué pasa si las variables locales no se desechan cuando salimos de una función? Esto implica que podemos
resume the function
donde lo dejamos. Aquí es dondegenerators
se introduce el concepto de y layield
declaración se reanuda donde lafunction
dejó.Esa es la diferencia entre
return
yyield
declaraciones en Python.La declaración de rendimiento es lo que hace que una función sea una función generadora.
Por lo tanto, los generadores son una herramienta simple y poderosa para crear iteradores. Se escriben como funciones regulares, pero usan la
yield
declaración cada vez que desean devolver datos. Cada vez que se llama a next (), el generador reanuda donde lo dejó (recuerda todos los valores de datos y qué declaración se ejecutó por última vez).fuente
Ejemplo del mundo real
Supongamos que tiene 100 millones de dominios en su tabla MySQL y desea actualizar el rango de Alexa para cada dominio.
Lo primero que necesita es seleccionar sus nombres de dominio de la base de datos.
Digamos que el nombre de su tabla es
domains
y el nombre de la columna esdomain
.Si lo usa
SELECT domain FROM domains
, devolverá 100 millones de filas, lo que consumirá mucha memoria. Entonces su servidor podría fallar.Entonces decidió ejecutar el programa en lotes. Digamos que nuestro tamaño de lote es 1000.
En nuestro primer lote, consultaremos las primeras 1000 filas, verificaremos el rango de Alexa para cada dominio y actualizaremos la fila de la base de datos.
En nuestro segundo lote trabajaremos en las siguientes 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:
Como puede ver, nuestra función mantiene
yield
los resultados. Si utilizó la palabra clave enreturn
lugar deyield
, entonces toda la función finalizaría una vez que alcanzara el retorno.Si una función usa la palabra clave
yield
entonces es un generador.Ahora puedes iterar así:
fuente
Buffering. Cuando es eficiente obtener datos en fragmentos grandes, pero procesarlos en fragmentos pequeños, un generador podría ayudar:
Lo anterior le permite separar fácilmente el almacenamiento en búfer del procesamiento. La función de consumidor ahora puede obtener los valores uno por uno sin preocuparse por el almacenamiento en búfer.
fuente
He descubierto que los generadores son muy útiles para limpiar su código y al brindarle una forma ú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 parte de su código (y no solo dentro de un bucle o un bloque, por ejemplo), los generadores son la característica para utilizar.
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:
Ahora tiene dos objetos generadores de números de Fibonacci a los que puede llamar desde cualquier parte de su código y siempre devolverán números de Fibonacci cada vez más grandes en secuencia de la siguiente manera:
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 ellas es como "funciones" que recuerdan su estado interno.
Obtuve el ejemplo de Fibonacci de Python Generators. ¿Qué son? y con un poco de imaginación, puede encontrar muchas otras situaciones en las que los generadores son una excelente alternativa a los
for
bucles y otras construcciones de iteración tradicionales.fuente
La explicación simple: considere una
for
declaraciónLa mayoría de las veces, todos los elementos
iterable
no necesitan estar allí desde el principio, sino que se pueden generar sobre la marcha según sea necesario. Esto puede ser mucho más eficiente en ambosOtras veces, ni siquiera conoce todos los elementos con anticipación. Por ejemplo:
No tiene forma de conocer todos los comandos del usuario de antemano, pero puede usar un buen ciclo como este si tiene un generador que le entrega los comandos:
Con los generadores también puede tener iteración sobre secuencias infinitas, lo que, por supuesto, no es posible al iterar sobre contenedores.
fuente
itertool
para eso, miracycles
.Mis usos favoritos son las operaciones de "filtro" y "reducción".
Digamos que estamos leyendo un archivo y solo queremos las líneas que comienzan con "##".
Entonces podemos usar la función de generador en un bucle apropiado
El ejemplo de reducción es similar. Digamos que tenemos un archivo donde necesitamos ubicar bloques de
<Location>...</Location>
líneas. [No son etiquetas HTML, sino líneas que parecen etiquetas.]Nuevamente, podemos usar este generador en un bucle apropiado para.
La idea es que una función de generador nos permite filtrar o reducir una secuencia, produciendo otra secuencia de un valor a la vez.
fuente
fileobj.readlines()
leería todo el archivo en una lista en la memoria, frustrando el propósito de usar generadores. Como los objetos de archivo ya son iterables, puede usarlosfor b in your_generator(fileobject):
en su lugar. De esa manera, su archivo se leerá una línea a la vez, para evitar leer el archivo completo.Un ejemplo práctico en el que podría utilizar 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:
Ahora puedo crear un rectángulo y recorrer sus esquinas:
En lugar de
__iter__
usted podría tener un métodoiter_corners
y llamarlo confor corner in myrect.iter_corners()
. Es más elegante de usar__iter__
ya que podemos usar el nombre de instancia de clase directamente en lafor
expresión.fuente
Básicamente evitando las funciones de devolución de llamada cuando se itera sobre el estado de mantenimiento de entrada.
Consulte aquí y aquí para obtener una descripción general de lo que se puede hacer con generadores.
fuente
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.
fuente
Como no se ha mencionado el método de envío de un generador, aquí hay un ejemplo:
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 (que incluye
yield
desde la expiración, generadores para procesamiento paralelo, escapar del límite de recursión, etc.)David Beazley en generadores en PyCon 2014
fuente
Uso generadores cuando nuestro servidor web actúa como proxy:
fuente
Montones de cosas. Cada vez que desee generar una secuencia de elementos, pero no desee tener que 'materializarlos' en una lista a la vez. Por ejemplo, podría tener un generador simple que devuelva números primos:
Luego puede usar eso para generar los productos de primos posteriores:
Estos son ejemplos bastante triviales, pero puede ver cómo puede ser útil para procesar grandes conjuntos de datos (¡potencialmente infinitos!) Sin generarlos de antemano, que es solo uno de los usos más obvios.
fuente
También es bueno para imprimir los números primos hasta n:
fuente