¿Cuál es la diferencia entre iteradores y generadores? Algunos ejemplos de cuándo usaría cada caso serían útiles.
¿Cuál es la diferencia entre iteradores y generadores? Algunos ejemplos de cuándo usaría cada caso serían útiles.
Respuestas:
iterator
es un concepto más general: cualquier objeto cuya clase tiene un next
método ( __next__
en Python 3) y un __iter__
método que sí return self
.
Cada generador es un iterador, pero no al revés. Un generador se construye llamando a una función que tiene una o más yield
expresiones ( yield
declaraciones, en Python 2.5 y anteriores), y es un objeto que cumple con la definición de un párrafo anterior iterator
.
Es posible que desee utilizar un iterador, en lugar de un generador, cuando se necesita una clase con un comportamiento un tanto complejo de mantener el estado, o si quiere exponer a otros métodos además next
(y __iter__
, y __init__
). Muy a menudo, un generador (a veces, para necesidades suficientemente simples, una expresión de generador ) es suficiente, y es más simple de codificar porque el mantenimiento del estado (dentro de límites razonables) es básicamente "hecho por usted" cuando el marco se suspende y se reanuda.
Por ejemplo, un generador como:
def squares(start, stop):
for i in range(start, stop):
yield i * i
generator = squares(a, b)
o la expresión generadora equivalente (genexp)
generator = (i*i for i in range(a, b))
tomaría más código para construir como un iterador personalizado:
class Squares(object):
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __iter__(self): return self
def next(self): # __next__ in Python 3
if self.start >= self.stop:
raise StopIteration
current = self.start * self.start
self.start += 1
return current
iterator = Squares(a, b)
Pero, por supuesto, con la clase Squares
podría ofrecer fácilmente métodos adicionales, es decir
def current(self):
return self.start
si tiene alguna necesidad real de dicha funcionalidad adicional en su aplicación.
for ... in ...:
, pasado a una función, o iter.next()
for..in
sintaxis. Tal vez me faltaba algo, pero fue hace algún tiempo, no recuerdo si lo resolví. ¡Gracias!
¿Cuál es la diferencia entre iteradores y generadores? Algunos ejemplos de cuándo usaría cada caso serían útiles.
En resumen: los iteradores son objetos que tienen un método a __iter__
y a __next__
( next
en Python 2). Los generadores proporcionan una forma fácil e integrada de crear instancias de iteradores.
Una función con rendimiento sigue siendo una función que, cuando se llama, devuelve una instancia de un objeto generador:
def a_function():
"when called, returns generator object"
yield
Una expresión generadora también devuelve un generador:
a_generator = (i for i in range(0))
Para una exposición más profunda y ejemplos, sigue leyendo.
Específicamente, el generador es un subtipo de iterador.
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True
Podemos crear un generador de varias maneras. Una forma muy común y sencilla de hacerlo es con una función.
Específicamente, una función con rendimiento es una función que, cuando se llama, devuelve un generador:
>>> def a_function():
"just a function definition with yield in it"
yield
>>> type(a_function)
<class 'function'>
>>> a_generator = a_function() # when called
>>> type(a_generator) # returns a generator
<class 'generator'>
Y un generador, de nuevo, es un iterador:
>>> isinstance(a_generator, collections.Iterator)
True
Un iterador es un Iterable,
>>> issubclass(collections.Iterator, collections.Iterable)
True
que requiere un __iter__
método que devuelve un iterador:
>>> collections.Iterable()
Traceback (most recent call last):
File "<pyshell#79>", line 1, in <module>
collections.Iterable()
TypeError: Can't instantiate abstract class Iterable with abstract methods __iter__
Algunos ejemplos de iterables son las tuplas integradas, listas, diccionarios, conjuntos, conjuntos congelados, cadenas, cadenas de bytes, conjuntos de bytes, rangos y vistas de memoria:
>>> all(isinstance(element, collections.Iterable) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
next
o__next__
En Python 2:
>>> collections.Iterator()
Traceback (most recent call last):
File "<pyshell#80>", line 1, in <module>
collections.Iterator()
TypeError: Can't instantiate abstract class Iterator with abstract methods next
Y en Python 3:
>>> collections.Iterator()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Iterator with abstract methods __next__
Podemos obtener los iteradores de los objetos integrados (u objetos personalizados) con la iter
función:
>>> all(isinstance(iter(element), collections.Iterator) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
Se __iter__
llama al método cuando intenta utilizar un objeto con un ciclo for. Luego __next__
se llama al método en el objeto iterador para obtener cada elemento para el bucle. El iterador se eleva StopIteration
cuando lo ha agotado, y no puede reutilizarse en ese punto.
Desde la sección Tipos de generador de la sección Tipos de iterador de la documentación de Tipos incorporados :
Los generadores de Python proporcionan una forma conveniente de implementar el protocolo iterador. Si el
__iter__()
método de un objeto contenedor se implementa como un generador, devolverá automáticamente un objeto iterador (técnicamente, un objeto generador) que suministra los métodos__iter__()
ynext()
[__next__()
en Python 3]. Se puede encontrar más información sobre generadores en la documentación de la expresión de rendimiento.
(Énfasis añadido.)
Entonces, de esto aprendemos que los generadores son un tipo (conveniente) de iterador.
Puede crear un objeto que implemente el protocolo Iterator creando o extendiendo su propio objeto.
class Yes(collections.Iterator):
def __init__(self, stop):
self.x = 0
self.stop = stop
def __iter__(self):
return self
def next(self):
if self.x < self.stop:
self.x += 1
return 'yes'
else:
# Iterators must raise when done, else considered broken
raise StopIteration
__next__ = next # Python 3 compatibility
Pero es más fácil simplemente usar un generador para hacer esto:
def yes(stop):
for _ in range(stop):
yield 'yes'
O quizás más simple, una Expresión de generador (funciona de manera similar a las comprensiones de listas):
yes_expr = ('yes' for _ in range(stop))
Todos se pueden usar de la misma manera:
>>> stop = 4
>>> for i, y1, y2, y3 in zip(range(stop), Yes(stop), yes(stop),
('yes' for _ in range(stop))):
... print('{0}: {1} == {2} == {3}'.format(i, y1, y2, y3))
...
0: yes == yes == yes
1: yes == yes == yes
2: yes == yes == yes
3: yes == yes == yes
Puede usar el protocolo Iterator directamente cuando necesite extender un objeto Python como un objeto sobre el que se puede iterar.
Sin embargo, en la gran mayoría de los casos, es mejor usarlo yield
para definir una función que devuelve un iterador generador o considerar expresiones de generador.
Finalmente, tenga en cuenta que los generadores proporcionan aún más funcionalidad como corutinas. Explico los Generadores, junto con la yield
declaración, en profundidad sobre mi respuesta a "¿Qué hace la palabra clave" rendimiento "?".
Iteradores:
Los iteradores son objetos que utilizan el next()
método para obtener el siguiente valor de secuencia.
Generadores:
Un generador es una función que produce o produce una secuencia de valores utilizando el yield
método.
Cada next()
llamada al método en el objeto generador (por ejemplo: f
como en el ejemplo a continuación) devuelto por la función del generador (por ejemplo: foo()
función en el ejemplo a continuación), genera el siguiente valor en secuencia.
Cuando se llama a una función generadora, devuelve un objeto generador sin siquiera comenzar la ejecución de la función. Cuando next()
se llama al método por primera vez, la función comienza a ejecutarse hasta que alcanza la declaración de rendimiento que devuelve el valor obtenido. El rendimiento realiza un seguimiento, es decir, recuerda la última ejecución. Y la segunda next()
llamada continúa desde el valor anterior.
El siguiente ejemplo demuestra la interacción entre el rendimiento y la llamada al siguiente método en el objeto generador.
>>> def foo():
... print "begin"
... for i in range(3):
... print "before yield", i
... yield i
... print "after yield", i
... print "end"
...
>>> f = foo()
>>> f.next()
begin
before yield 0 # Control is in for loop
0
>>> f.next()
after yield 0
before yield 1 # Continue for loop
1
>>> f.next()
after yield 1
before yield 2
2
>>> f.next()
after yield 2
end
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
Agregar una respuesta porque ninguna de las respuestas existentes aborda específicamente la confusión en la literatura oficial.
Las funciones generadoras son funciones ordinarias definidas usando enyield
lugar dereturn
. Cuando se llama, una función generadora devuelve un objeto generador , que es un tipo de iterador, tiene unnext()
método. Cuando llamanext()
, se devuelve el siguiente valor producido por la función del generador.
La función o el objeto pueden llamarse "generador" dependiendo del documento fuente de Python que lea. El glosario de Python dice funciones generadoras, mientras que la wiki de Python implica objetos generadores. El tutorial de Python logra notablemente implicar ambos usos en el espacio de tres oraciones:
Los generadores son una herramienta simple y poderosa para crear iteradores. Se escriben como funciones regulares, pero usan la declaración de rendimiento cuando quieren 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).
Las primeras dos oraciones identifican generadores con funciones generadoras, mientras que la tercera oración los identifica con objetos generadores.
A pesar de toda esta confusión, uno puede buscar la referencia del lenguaje Python para la palabra clara y final:
La expresión de rendimiento solo se usa al definir una función generadora, y solo se puede usar en el cuerpo de una definición de función. Usar una expresión de rendimiento en una definición de función es suficiente para hacer que esa definición cree una función generadora en lugar de una función normal.
Cuando se llama a una función generadora, devuelve un iterador conocido como generador. Ese generador controla la ejecución de una función generadora.
Entonces, en un uso formal y preciso, "generador" no calificado significa objeto generador, no función generador.
Las referencias anteriores son para Python 2 pero la referencia del lenguaje Python 3 dice lo mismo. Sin embargo, el glosario de Python 3 establece que
generador ... Por lo general, se refiere a una función generadora, pero puede referirse a un iterador generador en algunos contextos. En los casos en que el significado deseado no está claro, el uso de los términos completos evita la ambigüedad.
Todos tienen una respuesta realmente agradable y detallada con ejemplos y realmente lo aprecio. Solo quería dar unas pocas líneas de respuesta para las personas que aún no tienen una idea conceptual clara:
Si crea su propio iterador, es un poco complicado: debe crear una clase y al menos implementar el iter y los siguientes métodos. Pero, ¿qué pasa si no quieres pasar por esta molestia y quieres crear rápidamente un iterador? Afortunadamente, Python proporciona una forma abreviada para definir un iterador. Todo lo que necesita hacer es definir una función con al menos 1 llamada para ceder y ahora, cuando llame a esa función, devolverá " algo " que actuará como un iterador (puede llamar al siguiente método y usarlo en un bucle for). Este algo tiene un nombre en Python llamado Generador
Espero que eso aclare un poco.
Las respuestas anteriores omitieron esta adición: un generador tiene un close
método, mientras que los iteradores típicos no. El close
método desencadena una StopIteration
excepción en el generador, que puede quedar atrapado en una finally
cláusula en ese iterador, para tener la oportunidad de ejecutar una limpieza. Esta abstracción lo hace más utilizable en iteradores grandes que simples. Uno puede cerrar un generador como podría cerrar un archivo, sin tener que preocuparse por lo que hay debajo.
Dicho esto, mi respuesta personal a la primera pregunta sería: iteratable solo tiene un __iter__
método, los iteradores típicos solo tienen un __next__
método, los generadores tienen tanto an __iter__
como ay __next__
adicional close
.
Para la segunda pregunta, mi respuesta personal sería: en una interfaz pública, tiendo a favorecer mucho a los generadores, ya que es más resistente: el close
método es más fácil de componer yield from
. A nivel local, puedo usar iteradores, pero solo si es una estructura plana y simple (los iteradores no se componen fácilmente) y si hay razones para creer que la secuencia es bastante corta, especialmente si se puede detener antes de llegar al final. Tiendo a ver los iteradores como una primitiva de bajo nivel, excepto como literales.
Para el flujo de control, los generadores son un concepto tan importante como las promesas: ambos son abstractos y componibles.
__iter__
método, ¿cómo es que un iterador __next__
solo puede tenerlo ? Si se supone que son iterables, esperaría que necesariamente lo tengan __iter__
también.
__iter__
en iterables para devolver un iterador, que solo requiere un next
método ( __next__
en Python3). No confunda los estándares (para escribir en un pato) con su implementación (cómo lo implementó un intérprete de Python en particular). Esto es un poco como la confusión entre las funciones del generador (definición) y los objetos del generador (implementación). ;)
Función de generador, objeto generador, generador:
Una función de generador es como una función normal en Python, pero contiene una o más yield
declaraciones. Las funciones de generador son una gran herramienta para crear objetos Iterator lo más fácil posible. El objeto iterador returend por función de generador también se denomina objeto generador o generador .
En este ejemplo, he creado una función Generador que devuelve un objeto Generador <generator object fib at 0x01342480>
. Al igual que otros iteradores, los objetos Generator pueden usarse en un for
bucle o con la función incorporada next()
que devuelve el siguiente valor del generador.
def fib(max):
a, b = 0, 1
for i in range(max):
yield a
a, b = b, a + b
print(fib(10)) #<generator object fib at 0x01342480>
for i in fib(10):
print(i) # 0 1 1 2 3 5 8 13 21 34
print(next(myfib)) #0
print(next(myfib)) #1
print(next(myfib)) #1
print(next(myfib)) #2
Entonces, una función generadora es la forma más fácil de crear un objeto Iterator.
Iterador :
Cada objeto generador es un iterador pero no al revés. Se puede crear un objeto iterador personalizado si su clase implementa __iter__
y __next__
método (también llamado protocolo iterador).
Sin embargo, es mucho más fácil usar la función de generadores para crear iteradores porque simplifican su creación, pero un Iterador personalizado le brinda más libertad y también puede implementar otros métodos de acuerdo con sus requisitos, como se muestra en el siguiente ejemplo.
class Fib:
def __init__(self,max):
self.current=0
self.next=1
self.max=max
self.count=0
def __iter__(self):
return self
def __next__(self):
if self.count>self.max:
raise StopIteration
else:
self.current,self.next=self.next,(self.current+self.next)
self.count+=1
return self.next-self.current
def __str__(self):
return "Generator object"
itobj=Fib(4)
print(itobj) #Generator object
for i in Fib(4):
print(i) #0 1 1 2
print(next(itobj)) #0
print(next(itobj)) #1
print(next(itobj)) #1
Ejemplos de Ned Batchelder muy recomendados para iteradores y generadores.
Un método sin generadores que hace algo a los números pares
def evens(stream):
them = []
for n in stream:
if n % 2 == 0:
them.append(n)
return them
mientras usando un generador
def evens(stream):
for n in stream:
if n % 2 == 0:
yield n
return
declaraciónLlamar al evens
método (generador) es como siempre
num = [...]
for n in evens(num):
do_smth(n)
Iterador
Un libro lleno de páginas es iterable , un marcador es un iterador
y este marcador no tiene nada que hacer excepto moverse next
litr = iter([1,2,3])
next(litr) ## 1
next(litr) ## 2
next(litr) ## 3
next(litr) ## StopIteration (Exception) as we got end of the iterator
Para usar Generator ... necesitamos una función
Para usar Iterator ... necesitamos next
yiter
Como se ha dicho:
Una función de generador devuelve un objeto iterador
Todo el beneficio de Iterator:
Almacene un elemento a la vez en la memoria
Puede comparar ambos enfoques para los mismos datos:
def myGeneratorList(n):
for i in range(n):
yield i
def myIterableList(n):
ll = n*[None]
for i in range(n):
ll[i] = i
return ll
# Same values
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
for i1, i2 in zip(ll1, ll2):
print("{} {}".format(i1, i2))
# Generator can only be read once
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))
# Generator can be read several times if converted into iterable
ll1 = list(myGeneratorList(10))
ll2 = myIterableList(10)
print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))
Además, si verifica la huella de la memoria, el generador toma mucha menos memoria ya que no necesita almacenar todos los valores en la memoria al mismo tiempo.
Estoy escribiendo específicamente para los novatos de Python de una manera muy simple, aunque en el fondo Python hace muchas cosas.
Comencemos con lo muy básico:
Considera una lista,
l = [1,2,3]
Escribamos una función equivalente:
def f():
return [1,2,3]
o / p de print(l): [1,2,3]
& o / p deprint(f()) : [1,2,3]
Hagamos que la lista sea iterable: en Python, la lista siempre es iterable, lo que significa que puede aplicar el iterador cuando lo desee.
Apliquemos el iterador en la lista:
iter_l = iter(l) # iterator applied explicitly
Hagamos que una función sea iterable, es decir, escriba una función generadora equivalente.
En python tan pronto como introduzca la palabra clave yield
; se convierte en una función generadora y el iterador se aplicará implícitamente.
Nota: Cada generador siempre es iterable con el iterador implícito aplicado y aquí el iterador implícito es el punto crucial. Por lo tanto, la función del generador será:
def f():
yield 1
yield 2
yield 3
iter_f = f() # which is iter(f) as iterator is already applied implicitly
Entonces, si has observado, tan pronto como hiciste la función fa generador, ya es iter (f)
Ahora,
l es la lista, después de aplicar el método iterador "iter" se convierte en iter (l)
f ya es iter (f), después de aplicar el método iterador "iter" se convierte en iter (iter (f)), que nuevamente es iter (f)
Es como si estuvieras convirtiendo int a int (x) que ya es int y seguirá siendo int (x).
Por ejemplo o / p de:
print(type(iter(iter(l))))
es
<class 'list_iterator'>
Nunca olvides que esto es Python y no C o C ++
Por lo tanto, la conclusión de la explicación anterior es:
lista l ~ = iter (l)
función de generador f == iter (f)