kb84tkhrのブログ

何を書こうか考え中です あ、あと組織とは関係ないってやつです 個人的なやつ

ジェネレータ(続き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がプリミティブってことでいいんじゃないかと
そう思えばちょっとすっきりする