ジェネレータ(続き3)
まだ、yield from
っていうのが残ってた
When yield from
is used, it treats the supplied expression as a subiterator.
(yield from <式>が使われると、与えられた式はサブイテレータとして扱われる)
と言われてもよくわからなかったんだけれども
別のジェネレータをパーツとして扱うんだ、と言われてピンときた
たとえば、こんなジェネレータがあったとして
def up_gen(n: int) -> Generator[int, None, None]:
for i in range(n):
yield i
def down_gen(n: int) -> Generator[int, None, None]:
for i in reversed(range(n)):
yield i
これらを組み合わせたジェネレータを作りたいと思ってもこうは書けない
def up_down_gen(n: int) -> Generator[int, None, None]:
up_gen(n)
down_gen(n)
なにしろ1回間違えたから間違いない
$ python3 -i yieldfrom.py
Traceback (most recent call last):
File "practice.py", line 21, in <module>
[i for i in up_down_gen(3)]
TypeError: 'NoneType' object is not iterable
何も返してないんだから当然だ
こう書く
def up_down_gen(n: int) -> Generator[int, None, None]:
yield from up_gen(n)
yield from down_gen(n)
$ python3 -i yieldfrom.py
>>> [i for i in up_gen(3)]
[0, 1, 2]
>>> [i for i in down_gen(3)]
[2, 1, 0]
>>> [i for i in up_down_gen(3)]
[0, 1, 2, 2, 1, 0]
これはすごく簡単な例だけど
O'ReilleyのPython Cookbookの4.4. Implementing the Iterator Protocolの例を見て
ちょっと感激したので丸写ししておく
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
そうかー木もこうやってジェネレータにできるんだーと思いましたとさ
確かどこかでそういうのやりたかったけどあきらめた気がする
見つけ出してリベンジしたい
実行結果(これもまる写し)
$ python3 -i depth.py
>>> root = Node(0)
>>> child1 = Node(1)
>>> child2 = Node(2)
>>> root.add_child(child1)
>>> root.add_child(child2)
>>> child1.add_child(Node(3))
>>> child1.add_child(Node(4))
>>> child2.add_child(Node(5))
>>> for ch in root.depth_first():
... print(ch)
...
Node(0)
Node(1)
Node(3)
Node(4)
Node(2)
Node(5)
Python Cookbookには、これをイテレータプロトコルで書くと
いかにめんどくさいかという例も書いてある
ジェネレータがいまひとつわかった気がしてなかったのは
イテレータがわかってないからかなと思ってたけど
ジェネレータにとって、イテレータは特に本質的な構成要素って
わけじゃないのではという気がしてきている
yieldがプリミティブってことでいいんじゃないかと
そう思えばちょっとすっきりする