イテラブルはループ可能なもの全般(リスト、文字列、辞書)で、イテレータは実際の反復処理を行うオブジェクトであり、値を 1 つずつ生成し、自身の位置を追跡します。この区別が Python の for ループの動作の基盤になっています。
2 つのプロトコル
text
Iterable → has __iter__() → returns an iterator
Iterator → has __next__() → returns the next value (and __iter__ returning itself)
for ループが実際にどう動くか
python
# this for loop...
for x in [1, 2, 3]:
print(x)
# ...is roughly equivalent to:
it = iter([1, 2, 3]) # __iter__ → get an iterator from the iterable
while True:
try:
x = next(it) # __next__ → get the next value
print(x)
except StopIteration: # raised when exhausted → loop ends
break
for ループは iter() を呼んでイテレータを取得し、StopIteration が終わりを知らせるまで next() を繰り返し呼びます。このプロトコルが、あらゆるイテラブルをループ可能にしています。
実際のイテラブル vs イテレータ
python
nums = [1, 2, 3] # an ITERABLE (a list)
it = iter(nums) # an ITERATOR over it
next(it) # 1
next(it) # 2
next(it) # 3
next(it) # StopIteration
# the list is REUSABLE (get a fresh iterator each loop);
# an iterator is CONSUMED ONCE — exhausted after one pass
list(it) # [] — already used up
重要な違い: イテラブル(リスト)は何度もループできます(各 for が新しいイテレータを取得する)。一方、イテレータは使い捨てです。一度使い切ると空になります。
カスタムイテレータを作る
python
class Countdown:
def __init__(self, start): self.n = start
def __iter__(self): return self # the iterable returns itself as iterator
def __next__(self):
if self.n <= 0:
raise StopIteration # signal the end
self.n -= 1
return self.n + 1
for x in Countdown(3): # 3, 2, 1
print(x)
ジェネレータはイテレータである
python
def gen(): yield 1; yield 2 # a generator function returns an iterator automatically
ジェネレータはイテレータを作る簡単な方法です。yield が __next__/StopIteration の機構を肩代わりしてくれます。
なぜ重要なのか
イテラブル/イテレータの区別は、Python で for ループがどう動くか、そしてイテレータプロトコルを介してあらゆるコレクション(リスト、辞書、ファイル、ジェネレータ)で反復処理がどのように統一されているかを説明します。
これを理解すると実際の挙動が明確になります。なぜイテレータは一度しか消費できないのか(よくあるバグ: ジェネレータや zip を二度ループすると二度目は何も得られない)、なぜジェネレータはイテレータなのか、そして自作のオブジェクトをどうイテラブルにするか、です。
このプロトコルは Python のエレガントで一貫した反復処理モデルの基盤であり、ジェネレータ、遅延評価、カスタムなイテラブルクラスを扱ううえで不可欠です。
