読者です 読者をやめる 読者になる 読者になる

素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: すかいぷ:javascripter_  か javascripter あっと tsukkun.net skypeのほうがいいです

rangeとジェネレータとzip

python

range()オブジェクトとジェネレータの違いについて調べた。

def enum_from_to(begin, end):
  i = begin
  while i < end:
    yield i
    i += 1

まず、rangeの機能を限定したようなジェネレータを定義する。

print (list(zip(enum_from_to(0, 10), enum_from_to(0, 10))))
# => [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]

同じ中身を持つジェネレータをzipすると上のようになる。

L = enum_from_to(0, 10)
print (list(zip(L, L)))
# => [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]

同じジェネレータをzipすると、.next()によって一つのジェネレータオブジェクトが破壊的に進められるので、上のようになる。

では、rangeをzipするとどうなるか。

L = range(0, 10)
# Python2系の場合はxrangeを使う
print (list(zip(L, L)))
# => [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]

何故か同じLを使っているのに、zipの挙動がenum_from_toを使った場合と違う。気になったので、ソースを見て調べることにする。

git svn clone -s -r HEAD http://svn.python.org/projects/python/ python           

上記のコマンドは、trunkなどのディレクトリ構造をブランチに対応付けさせ、リビジョンのHEADのみを取ってこい、という意味だ。
git grep rangeして、/Demo/classes/Range.pyを見つけた。Demoに入ってるので実際に使われるわけではないが(実際にはbltinmodule.cの中の関数が使われる)、実装方法はほとんど同じなようだ。

    def __getitem__(self, i): 
        """implement x[i]"""
        if 0 <= i <= self.len:
            return self.start + self.step * i 
        else:
            raise IndexError, 'range[i] index out of range'

そもそもrange()はジェネレータではなく、next()が呼ばれているわけでもなかった。__getitem__でインスタンス変数を触っていないので、zipで並行して取得しても不整合が起きない。というわけで、range()はジェネレータではない。
ジェネレータは無限リストのように使える事もあるが、.next()が破壊的であることを意識しないと、たまにハマる。