当サイトは、アフィリエイト広告を利用しています
Pythonの反復処理は、一見するとfor文だけで完結しているように見える。
しかし内部では明確な構造が存在する。
これらは別物である。
混同しやすいが、関係性は階層構造になっている。
Iterable└── Iterator└── Generator
また前提として、for文は内部的下記の構造で動いてる
it = iter(obj)while True:try:value = next(it)except StopIteration:break
この仕組みを理解すると
を理解することができる
当記事では
について、反復モデルの内部構造という観点から整理する。
イテラブルとは反復可能なオブジェクトのこと。
具体的には
といった反復可能なオブジェクトがイテラブルとなる。
つまりイテラブルは「材料」のこと
イテラブルに関する詳細は下記記事参照
イテレータとは、イテラブルから生成される値を1つずつ返すオブジェクトである。
listを例にして説明すると
# イテラブル生成list = [1, 2, 3]# iter()を呼ぶとイテレータを返すit = iter(list)# イテレータから値を取得(next)print(next(it)) # 1print(next(it)) # 2print(next(it)) # 3print(next(it)) # StopIteration# イテラブル作成tuple = (1, 2, 3)# iter()を呼ぶとイテレータを返すit = iter(tuple)# イテレータから値を取得(for文)for value in it:print(value) # 1# 2# 3
イテレータは「順に値を返す仕組み」である。
※イテレータ自体は遅延評価ではない
イテレータに関してさらに深く解説した記事は下記参照
yieldによって作られるイテレータのこと。
つまりジェネレータはイテレータの一種である。
※ジェネレータはイテレータに含まれる
ジェネレータの生成方法は
の2種類ある
def generator():yield 1yield 2yield 3
generator = (value for value in list([1, 2, 3]))
yieldは
のように必要になった瞬間にだけ進む構造を実現する。
yield を含む関数はジェネレータ関数となり、
呼び出すとジェネレータ(イテレータ)を返す。
ジェネレータは next() が呼ばれたときにのみ処理を再開し、次の yield まで実行される。
つまり処理は「必要になった瞬間」にだけ進む逐次実行モデルを持つ。
この実行モデルにより、計算の評価が next() 呼び出し時まで延期される。
これを遅延評価(lazy evaluation)と呼ぶ。
なお、遅延評価はイテレータの定義そのものではない。
イテレータは単に「次の値を返す仕組み」であり、
ジェネレータはその中でも遅延評価を代表的に実現するイテレータである。
※イテレータはあくまで取り出し方の仕組み
遅延評価とは、
Pythonではジェネレータが代表例である。
# イテレータ生成lst = [x for x in [1, 2, 3]]print(lst) # [1, 2, 3]
このコードではリスト内包表記が実行された瞬間に
すべての要素が計算され
メモリ上にリストが生成される
つまり、必要になる前に全要素を生成、評価している(即時評価)
# ジェネレータ生成gen = (x for x in [1, 2, 3])print(gen) # <generator object ...>
print(next(gen)) # 1print(next(gen)) # 2print(next(gen)) # 3
なお、この例では元のリスト [1,2,3] は即時に生成されている。
遅延しているのは内包式の評価部分である。
たとえば下記のような場合は、より遅延的な構造になる。
gen = (x for x in range(10**9))
このように、元データと評価処理の両方が逐次的に進む構造を持つと、
より「完全な遅延評価」に近い動作となる。
遅延評価は「構造の連鎖」で決まる。
ジェネレータの有効活用の具体的な方法については下記記事でまとめている
Pythonの反復処理は
という役割分担の上に設計されている。
for 文は構文であり、その背後にはこの反復モデルが存在している。