que iteradores generadores generador funcion explicacion español creacion codigo python generator

iteradores - ¿Para qué puedes usar las funciones del generador de Python?



que es un iterable en python (16)

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


Ejemplo del mundo real

Digamos que tiene 100 millones de dominios en su tabla de MySQL, y le gustaría actualizar el rango de Alexa para cada dominio.

Lo primero que necesitas es seleccionar tus 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 usa el SELECT domain FROM domains , devolverá 100 millones de filas, lo que consumirá mucha memoria. Por lo que su servidor podría fallar.

Así que decidiste 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 de generador 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 puede ver, nuestra función sigue dando los resultados. Si usó el return la palabra clave en lugar del yield , la función completa se finalizaría una vez que alcanzara el retorno.

return - returns only once yield - returns multiple times

Si una función utiliza el yield la palabra clave, 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()


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.


Básicamente, se evitan las funciones de devolución de llamada cuando se itera sobre el estado de mantenimiento de entrada.

Consulte aquí y here para obtener una descripción general de lo que se puede hacer con los generadores.


Buffering. Cuando es eficiente obtener datos en grandes porciones, pero procesarlos en pequeñas porciones, 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 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.


Como 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 los generadores en el video a continuación (incluido el yield de la exploración, los generadores para el procesamiento en paralelo, el escape del límite de recursión, etc.)

David Beazley sobre generadores en PyCon 2014


Descubrí que los generadores son muy útiles para limpiar su código y al brindarle una manera única de encapsular y modular el código. En una situación en la que necesita algo para escupir constantemente valores en función de su propio procesamiento interno y cuando se debe llamar a ese elemento 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 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 = 1 yield first yield second while 1: 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:

>>> 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 crear objetos. Una forma de pensar acerca de ellos es como "funciones" que recuerdan su estado interno.

Obtuve el ejemplo de Fibonacci de los generadores de Python: ¿Qué son? y con un poco de imaginación, puede crear muchas otras situaciones en las que los generadores son una excelente alternativa for bucles y otras construcciones de iteración tradicionales.


Encuentro esta explicación que aclara mi duda. Porque existe la posibilidad de que la persona que no sabe Generators tampoco sepa sobre yield

Regreso

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

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 la dejamos. Aquí es donde se introduce el concepto de generators y la declaración de yield reanuda donde se detuvo la 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()

Esa es la diferencia entre las declaraciones de yield y yield en Python.

La declaración de rendimiento es lo que hace que una función sea una función generadora.

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


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

for item in iterable: do_stuff()

La mayoría de las veces, no es necesario que todos los elementos en iterable estén allí desde el principio, sino que se pueden generar sobre la marcha según sea necesario. Esto puede ser mucho más eficiente tanto en

  • 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 sabes todos los elementos antes de tiempo. Por ejemplo:

for command in user_input(): do_stuff_with(command)

No tiene forma de saber de antemano todos los comandos del usuario, pero puede usar un buen bucle como este si tiene un generador que le 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.


Los generadores te dan perezosa evaluación. Los usas iterando sobre ellos, ya sea explícitamente con ''for'' o implícitamente pasándolos 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 de una 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 en sí mismos) 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 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 los generadores (que en realidad es 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, se usaría una función de devolución de llamada para esto. Pasa esta devolución de llamada a la función de trabajo y llamaría periódicamente 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 por separado y pasarla a la función de trabajo, hace todo el trabajo de informes en un pequeño ''for'' loop alrededor del generador.

Por ejemplo, digamos que escribiste un programa de ''búsqueda de sistema de archivos''. Puede realizar la búsqueda en su totalidad, recopilar los resultados y luego mostrarlos uno por uno. 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 puede mostrar los resultados mientras los encuentra, lo que sería más eficiente en cuanto a la memoria y mucho más amigable para el usuario. Esto último se podría hacer pasando la función de impresión de resultados a la función de búsqueda del sistema de archivos, o se podría hacer simplemente haciendo de la función de búsqueda un generador e iterando el resultado.

Si desea ver un ejemplo de los dos últimos enfoques, vea os.path.walk () (la antigua función de desplazamiento del sistema de archivos con devolución de llamada) y os.walk () (el nuevo generador de desplazamiento 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 gran lista:

big_list = list(the_generator)


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

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 de reducción es similar. Digamos que tenemos un archivo donde necesitamos ubicar bloques de líneas <Location>...</Location> . [No son etiquetas HTML, sino líneas que parecen tener el mismo aspecto que una etiqueta].

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

Nuevamente, podemos usar este generador en un bucle apropiado.

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

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


Montones de cosas Cada vez que quiera generar una secuencia de elementos, pero no quiera 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) 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ías 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.


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)


Un ejemplo práctico en el que podría hacer uso de un generador es si tiene algún tipo de forma y desea recorrer sus esquinas, bordes o lo que sea. Para mi propio proyecto (código fuente here ) tuve 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 hacer un bucle sobre sus esquinas:

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

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


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.

La otra es tratar los resultados de uno en uno, evitando la creación de grandes listas de resultados que de todos modos procesaría por separado.

Si tiene una función de 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 como esta:

# 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 usas la función así:

for x in fibon(1000000): print x,

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


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

  1. El cliente solicita un url proxy del servidor
  2. El servidor comienza a cargar la URL de destino.
  3. El servidor cede para devolver los resultados al cliente tan pronto como los obtiene.

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 usar subprocesos.