python - tag - ¿Cómo escribir una clase de generador?
python tag cloud (4)
¿Cómo escribir una clase de generador?
Ya casi está ahí, escribiendo una clase de iterador (muestro un Generador al final de la respuesta), pero a __next__
se le llama cada vez que llama al objeto con next
, devolviendo un objeto generador. En su lugar, use __iter__
:
>>> class Fib:
... def __init__(self):
... self.a, self.b = 0, 1
... def __iter__(self):
... while True:
... yield self.a
... self.a, self.b = self.b, self.a+self.b
...
>>> f = iter(Fib())
>>> for i in range(3):
... print(next(f))
...
0
1
1
Para hacer de la clase misma un iterador:
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __next__(self):
return_value = self.a
self.a, self.b = self.b, self.a+self.b
return return_value
def __iter__(self):
return self
Y ahora:
>>> f = iter(Fib())
>>> for i in range(3):
... print(next(f))
...
0
1
1
¿Por qué el valor self.a no se imprime?
Aquí está tu código original con mis comentarios:
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __next__(self):
yield self.a # yield makes .__next__() return a generator!
self.a, self.b = self.b, self.a+self.b
f = Fib()
for i in range(3):
print(next(f))
Así que cada vez que llama a la next(f)
, obtiene el objeto generador que __next__
devuelve:
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
Además, ¿cómo escribo unittest para generadores?
Aún necesita implementar un método de envío y lanzamiento para un Generator
from collections import Iterator, Generator
import unittest
class Test(unittest.TestCase):
def test_Fib(self):
f = Fib()
self.assertEqual(next(f), 0)
self.assertEqual(next(f), 1)
self.assertEqual(next(f), 1)
self.assertEqual(next(f), 2) #etc...
def test_Fib_is_iterator(self):
f = Fib()
self.assertIsInstance(f, Iterator)
def test_Fib_is_generator(self):
f = Fib()
self.assertIsInstance(f, Generator)
Y ahora:
>>> unittest.main(exit=False)
..F
======================================================================
FAIL: test_Fib_is_generator (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "<stdin>", line 7, in test_Fib_is_generator
AssertionError: <__main__.Fib object at 0x00000000031A6320> is not an instance of <class ''collections.abc.Generator''>
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)
<unittest.main.TestProgram object at 0x0000000002CAC780>
Entonces, implementemos un objeto generador y aprovechemos la clase base abstracta del Generator
desde el módulo de colecciones (ver la fuente para su implementation ), lo que significa que solo necesitamos implementar el send
y el throw
, dándonos el close
, __iter__
(devuelve self) y __next__
(igual que .send(None)
) gratis (consulte el modelo de datos de Python en coroutines ):
class Fib(Generator):
def __init__(self):
self.a, self.b = 0, 1
def send(self, ignored_arg):
return_value = self.a
self.a, self.b = self.b, self.a+self.b
return return_value
def throw(self, type=None, value=None, traceback=None):
raise StopIteration
y utilizando las mismas pruebas anteriores:
>>> unittest.main(exit=False)
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
<unittest.main.TestProgram object at 0x00000000031F7CC0>
Python 2
ABC Generator
solo está en Python 3. Para hacer esto sin Generator
, debemos escribir al menos close
, __iter__
, y __next__
además de los métodos que definimos anteriormente.
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1
def send(self, ignored_arg):
return_value = self.a
self.a, self.b = self.b, self.a+self.b
return return_value
def throw(self, type=None, value=None, traceback=None):
raise StopIteration
def __iter__(self):
return self
def next(self):
return self.send(None)
def close(self):
"""Raise GeneratorExit inside generator.
"""
try:
self.throw(GeneratorExit)
except (GeneratorExit, StopIteration):
pass
else:
raise RuntimeError("generator ignored GeneratorExit")
Tenga en cuenta que copié directamente desde la biblioteca estándar de Python 3, sin modificaciones.
Veo muchos ejemplos de funciones de generador, pero quiero saber cómo escribir generadores para las clases. Digamos que quería escribir la serie de Fibonacci como una clase.
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __next__(self):
yield self.a
self.a, self.b = self.b, self.a+self.b
f = Fib()
for i in range(3):
print(next(f))
Salida:
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
¿Por qué el valor self.a
no se imprime? Además, ¿cómo escribo unittest
para generadores?
No use el yield
en la función __next__
y aplique a next
también para compatibilidad con python2.7 +
Código
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __next__(self):
a = self.a
self.a, self.b = self.b, self.a+self.b
return a
def next(self):
return self.__next__()
Si le da a la clase un __iter__()
implementado como generador , devolverá automáticamente un objeto generador cuando se le llame, de modo que los métodos __iter__
y __next__
del objeto serán los utilizados.
Esto es lo que quiero decir:
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
while True:
value, self.a, self.b = self.a, self.b, self.a+self.b
yield value
f = Fib()
for i, value in enumerate(f, 1):
print(value)
if i > 5:
break
Salida:
0
1
1
2
3
5
__next__
debe devolver un artículo, no cederlo.
Puede escribir lo siguiente, en el que Fib.__iter__
devuelve un iterador adecuado:
class Fib:
def __init__(self, n):
self.n = n
self.a, self.b = 0, 1
def __iter__(self):
for i in range(self.n):
yield self.a
self.a, self.b = self.b, self.a+self.b
f = Fib(10)
for i in f:
print i
o haga que cada instancia sea un iterador definiendo __next__
.
class Fib:
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
x = self.a
self.a, self.b = self.b, self.a + self.b
return x
f = Fib()
for i in range(10):
print next(f)