que - Construye un iterador Python básico
que es un iterable en python (9)
En primer lugar, el módulo de itertools es increíblemente útil para todo tipo de casos en los que un iterador sería útil, pero aquí está todo lo que necesita para crear un iterador en python:
rendimiento
¿No es genial? El rendimiento se puede utilizar para reemplazar un rendimiento normal en una función. Devuelve el objeto de la misma manera, pero en lugar de destruir el estado y salir, guarda el estado para cuando desea ejecutar la siguiente iteración. Este es un ejemplo de ello en acción extraído directamente de la lista de funciones de itertools :
def count(n=0):
while True:
yield n
n += 1
Como se indica en la descripción de funciones (es la función count () del módulo itertools ...), produce un iterador que devuelve números enteros consecutivos que comienzan con n.
Las expresiones del generador son otra lata de gusanos (¡gusanos impresionantes!). Se pueden usar en lugar de una Comprensión de lista para guardar la memoria (las comprensiones de lista crean una lista en la memoria que se destruye después de su uso si no se asigna a una variable, pero las expresiones del generador pueden crear un Objeto generador ... que es una forma elegante diciendo iterador). Aquí hay un ejemplo de una definición de expresión de generador:
gen = (n for n in xrange(0,11))
Esto es muy similar a nuestra definición de iterador anterior, excepto que el rango completo está predeterminado entre 0 y 10.
Acabo de encontrar xrange () (sorprendió que no lo había visto antes ...) y lo agregué al ejemplo anterior. xrange () es una versión iterable de range () que tiene la ventaja de no pre-construir la lista. Sería muy útil si tuviera un gigantesco cuerpo de datos para iterar y solo tuviera tanta memoria para hacerlo.
¿Cómo se crearía una función iterativa (u objeto iterador) en python?
Esta es una función iterable sin yield
. Hace uso de la función iter
y un cierre que mantiene su estado mutable ( list
) en el ámbito de aplicación de python 2.
def count(low, high):
counter = [0]
def tmp():
val = low + counter[0]
if val < high:
counter[0] += 1
return val
return None
return iter(tmp, None)
Para Python 3, el estado de cierre se mantiene de forma inmutable en el ámbito de cierre y nonlocal
se utiliza en el ámbito local para actualizar la variable de estado.
def count(low, high):
counter = 0
def tmp():
nonlocal counter
val = low + counter
if val < high:
counter += 1
return val
return None
return iter(tmp, None)
Prueba;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
Esta pregunta es sobre objetos iterables, no sobre iteradores. En Python, las secuencias también son iterables, por lo que una forma de hacer una clase iterable es hacer que se comporte como una secuencia, es decir, __len__
métodos __getitem__
y __len__
. He probado esto en Python 2 y 3.
class CustomRange:
def __init__(self, low, high):
self.low = low
self.high = high
def __getitem__(self, item):
if item >= len(self):
raise IndexError("CustomRange index out of range")
return self.low + item
def __len__(self):
return self.high - self.low
cr = CustomRange(0, 10)
for i in cr:
print(i)
Hay cuatro formas de construir una función iterativa:
- crear un generador (utiliza la palabra clave de rendimiento )
- usar una expresión generadora ( genexp )
- crear un iterador (define
__iter__
y__next__
(onext
en Python 2.x)) - crear una función que Python pueda iterar por su cuenta ( define
__getitem__
)
Ejemplos:
# generator
def uc_gen(text):
for char in text:
yield char.upper()
# generator expression
def uc_genexp(text):
return (char.upper() for char in text)
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index].upper()
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text
def __getitem__(self, index):
result = self.text[index].upper()
return result
Para ver los cuatro métodos en acción:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator(''abcde''):
print ch,
print
Lo que resulta en:
A B C D E
A B C D E
A B C D E
A B C D E
Nota :
Los dos tipos de generador ( uc_gen
y uc_genexp
) no se pueden reversed()
; el iterador simple ( uc_iter
) necesitaría el método mágico __reversed__
(que debe devolver un nuevo iterador que va hacia atrás); y el objeto iteratable ( uc_getitem
) debe tener el método mágico __len__
:
# for uc_iter
def __reversed__(self):
return reversed(self.text)
# for uc_getitem
def __len__(self)
return len(self.text)
Para responder a la pregunta secundaria del Coronel Panic sobre un iterador infinitamente evaluado perezosamente, aquí están esos ejemplos, utilizando cada uno de los cuatro métodos anteriores:
# generator
def even_gen():
result = 0
while True:
yield result
result += 2
# generator expression
def even_genexp():
return (num for num in even_gen()) # or even_iter or even_getitem
# not much value under these circumstances
# iterator protocol
class even_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
# getitem method
class even_getitem():
def __getitem__(self, index):
return index * 2
import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
limit = random.randint(15, 30)
count = 0
for even in iterator():
print even,
count += 1
if count >= limit:
break
print
Lo que resulta en (al menos para mi ejecución de muestra):
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
Inspirado por la respuesta de Matt Gregory, aquí hay un iterador un poco más complicado que devolverá a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz
class AlphaCounter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
alpha = '' abcdefghijklmnopqrstuvwxyz''
n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
if n_current > n_high:
raise StopIteration
else:
increment = True
ret = ''''
for x in self.current[::-1]:
if ''z'' == x:
if increment:
ret += ''a''
else:
ret += ''z''
else:
if increment:
ret += alpha[alpha.find(x)+1]
increment = False
else:
ret += x
if increment:
ret += ''a''
tmp = self.current
self.current = ret[::-1]
return tmp
for c in AlphaCounter(''a'', ''zzz''):
print(c)
Si buscas algo corto y simple, tal vez sea suficiente para ti:
class A(object):
def __init__(self, l):
self.data = l
def __iter__(self):
return iter(self.data)
ejemplo de uso:
In [3]: a = A([2,3,4])
In [4]: [i for i in a]
Out[4]: [2, 3, 4]
Todas las respuestas en esta página son realmente geniales para un objeto complejo. Pero para aquellos que contienen tipos iterables incorporados como atributos, como str
, list
, set
o dict
, o cualquier implementación de collections.Iterable
. collections.Iterable
, puede omitir ciertas cosas en su clase.
class Test(object):
def __init__(self, string):
self.string = string
def __iter__(self):
# since your string is already iterable
return (ch for ch in string)
Se puede utilizar como:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
Veo que algunos de ustedes se return self
en __iter__
. Solo quería señalar que __iter__
sí mismo puede ser un generador (eliminando así la necesidad de __next__
y generando excepciones de StopIteration
)
class range:
def __init__(self,a,b):
self.a = a
self.b = b
def __iter__(self):
i = self.a
while i < self.b:
yield i
i+=1
Por supuesto, aquí también se puede hacer un generador directamente, pero para clases más complejas puede ser útil.
Los objetos de iterador en python se ajustan al protocolo del iterador, lo que básicamente significa que proporcionan dos métodos: __iter__()
y next()
. El __iter__
devuelve el objeto iterador y se __iter__
implícitamente al comienzo de los bucles. El método next()
devuelve el siguiente valor y se invoca implícitamente en cada incremento de bucle. next()
genera una excepción StopIteration cuando no hay más valor para devolver, que se captura implícitamente mediante construcciones de bucle para detener la iteración.
Aquí hay un ejemplo simple de un contador:
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def next(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
for c in Counter(3, 8):
print c
Esto imprimirá:
3
4
5
6
7
8
Esto es más fácil de escribir usando un generador, como se explica en una respuesta anterior:
def counter(low, high):
current = low
while current <= high:
yield current
current += 1
for c in counter(3, 8):
print c
La salida impresa será la misma. Bajo el capó, el objeto generador es compatible con el protocolo del iterador y hace algo más o menos similar a la clase Contador.
El artículo de David Mertz, Iteradores y generadores simples , es una introducción bastante buena.