Jak napisać klasę generatora?
Widzę wiele przykładów funkcji generatora, ale chcę wiedzieć, jak pisać generatory dla klas. Powiedzmy, że chciałem napisać serię Fibonacciego jako klasę.
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))
Wyjście:
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
Dlaczego wartość self.a
nie jest drukowana? Jak napisać unittest
dla generatorów?
4 answers
Jak napisać klasę generatora?
Jesteś prawie na miejscu, pisząc klasę Iterator (pokazuję Generator na końcu odpowiedzi), ale {[10] } jest wywoływany za każdym razem, gdy wywołujesz obiekt z next
, zwracając obiekt generatora. Zamiast tego użyj __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
Aby sama klasa stała się iteratorem:
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
A teraz:
>>> f = iter(Fib())
>>> for i in range(3):
... print(next(f))
...
0
1
1
Dlaczego jest jaźnią wartości.a nie Drukowanie?
Oto twój oryginał kod z moimi komentarzami:
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))
Więc za każdym razem, gdy wywołujesz next(f)
otrzymujesz obiekt generatora, który__next__
zwraca:
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
<generator object __next__ at 0x000000000A3E4F68>
W tym celu należy wdrożyć metodę send and throw dlaRównież, jak napisać unittest dla generatorów?
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)
A teraz:
>>> 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>
Zaimplementujmy więc obiekt generatora i wykorzystajmy Generator
abstrakcyjną klasę bazową z modułu collections (zobacz źródło dla jego implementacja ), czyli wystarczy zaimplementować send
i throw
- dając nam close
, __iter__
(zwraca self) i __next__
(to samo co .send(None)
) za darmo (zobacz Model danych Pythona na coroutinach):
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
I używając tych samych testów powyżej:
>>> unittest.main(exit=False)
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
<unittest.main.TestProgram object at 0x00000000031F7CC0>
Python 2
ABC Generator
jest tylko w Pythonie 3. Aby to zrobić bez Generator
, musimy napisać co najmniej close
, __iter__
, i __next__
oprócz metod, które zdefiniowaliśmy powyżej.
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")
Zauważ, że skopiowałem close
bezpośrednio z biblioteki standardowej Pythona 3 , bez modyfikacji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-14 20:21:35
__next__
powinien zwrócić element, a nie oddać go.
Możesz napisać następujący tekst, w którym Fib.__iter__
zwraca odpowiedni iterator:
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
Lub uczynić każdą instancję iteratorem, definiując __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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-23 18:06:55
Nie używaj yield
w funkcji __next__
i zaimplementuj next
również dla zgodności z python2. 7+
Kod
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__()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-23 18:09:44
Jeśli podasz klasie metodę __iter__()
zaimplementowaną jako generator, automatycznie zwróci obiekt generatora, gdy zostanie wywołany, więc zostaną użyte metody __iter__
i __next__
.
Oto co mam na myśli:
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
Wyjście:
0
1
1
2
3
5
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-28 13:26:02