こんにちは。 Pythonに関する書籍を書く際や、人に説明するときに「リストはイテレーター」と言っていませんか? それは間違いです。僕自身も勘違いしちゃうことがよくあるので、記憶を整理する意味でもブログにまとめておきます (もし間違えていたら教えてくれると嬉しいです)。
とくに、「リストはイテレーターです」、「rangeはイテレーターです」、「dict.keys()はイテレーターを返します」、など間違いが多いので注意しましょう。
イテレータ警察だ、床に伏せて頭に手をのせろ。{1:2, 2:3}.keys()を「イテレータ」と解説した疑いで貴様を逮捕する。
— Atsuo Ishimoto (@atsuoishimoto) 2020年2月3日
リストはイテレーターではない
まず、リストはイテレーターではないということを感じてもらいます。
以下をPythonのインタプリターで実行してください。
TypeError: 'list' object is not an iterator
とエラーが表示されるはずです。
>>> l = [0, 1, 2, 3] >>> next(l) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'list' object is not an iterator
はい。Pythonさんもリストはイテレーターではないと言っています。
「forループで使えるものはイテレーターじゃないの?」 という疑問があるかもしれません。うーん、惜しい。 その答えは イテラブル(Iterable) です。
リストもrangeオブジェクトもdict.keys()も、「イテラブル(Iterable)」と言えば間違いありません。
イテラブルは for in ...
に渡せるものです。
早く答えが欲しい人へ
早い説明をします。イテラブルかイテレーターかの簡単な判別方法をお伝えします。 超ざっくりと説明すると、以下です。
iter(...)
できるものは、イテラブルnext(...)
できるものは、イテレーター
本に説明を書くときとかは、一度上記を実行しておくと安心かもしれません。 往々にして説明したいのは「イテラブル」だと思いますが。
じゃぁイテレーターって何?
イテレーターは反復するもので、その進行状況を持っているものです。 forループは指定されたイテラブルからイテレーターを取り出して、ループの1回1回を処理していきます。 おっと、意味がわかりませんね!(ブラウザを閉じないで!)
ではfor文の気持ちになってリストの処理をしましょう。 for文は以下のようにして、リストをループします。
>>> l = [0, 1, 2, 3] # これはリスト >>> l_iter = iter(l) # リストからlist_iteratorを取り出す >>> next(l_iter) 0 >>> next(l_iter) 1 >>> next(l_iter) 2 >>> next(l_iter) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
このようにリストイテレータを順次 next(...)
することで1つ1つリストの要素を取り出しています。
すべての要素が取り出されると、イテレーターは StopIteration
を送出します。
なぜこのようになっているかというと、簡単に言えばforループの繰り返し処理を制御するためです。 リストをループするときに、ループがどこまで進んでいるかを管理するためにリストイテレーターが使われています。
もしリスト自身がイテレーターであれば、1度ループしただけでリストはループできなくなってしまいます(ループの進行状況が管理されるので)。 そのために、リスト自身ではなくリストイテレーターさん(つまりイテレーター)にループの状況を管理してもらっています。Pythonってすごい!
イテレーターは同時にイテラブルでもあります。
なので、イテレーターは for in ...
に渡せます。この場合、イテレーターは iter(my_iterator)
をすると my_iterator
自身を返します。
また、for文は以下のようにも模式的に書けます。
この my_for
関数は、第一引数にイテラブルを、第二引数にforブロックの処理に相当する関数を渡します。
>>> def my_for(iterable, iter_func): ... it = iter(iterable) ... try: ... while True: ... el = next(it) ... iter_func(el) ... except StopIteration: ... pass ...
そうすると、forを使わずにforのような動きができます (あくまでも模式的にですが)。
>>> def print_pow(el): ... print(el ** 2) ... >>> my_for([0, 1, 2, 3], print_pow) 0 1 4 9
イテレーターって何があるの
じゃぁイテレーターって何があるの?と思われるかもしれません。
たとえばジェネレーターや BytesIO
などがイテレーターです。よく「ループしたら使い切られる」やつがイテレーターです。
- イテレーターではない(イテラブル)
- 文字列
- リスト
- タプル
- rangeオブジェクト
- 辞書、辞書.keys()、辞書.values()、辞書.items()
- 集合
- イテレーター(イテラブル)
- リストとかタプルとかrangeとかを
iter
したら返ってくるやつ - ジェネレーター
- ファイルとかのI/O
- リストとかタプルとかrangeとかを
ちなみに yield
を使った関数は「ジェネレーターを返す関数」であり、イテラブルではありません。