nikkie-ftnextの日記

イベントレポートや読書メモを発信

Pythonのイテラブルとイテレータ 〜for文の秘密〜

はじめに

京都リサーチパークにて開催のYAPC::Kyotoでノベルティを受け取りし参加者の1人、nikkieです。

Pythonはクラスに特殊メソッド1を定義することで、インスタンスに特別な振る舞いをさせることができます。
今回は、インスタンスfor文で使えるようにする実装まわりの自分の理解を整理します。

目次

イテラブル

「反復可能オブジェクト」とも呼ばれます。
https://docs.python.org/ja/3/glossary.html#term-iterable

Python実践入門』には、

一言で表すと、for文や内包表記で使えるオブジェクト (Kindle の位置No.3744 8.2)

という端的な説明がありました。

用語集(glossary)の例は分かりやすく感じます2

  • シーケンス(list, str, tupleなど)
  • dict
  • ファイルオブジェクト
  • など

イテラブルを反復するとき

では、イテラブルを反復するとき、何が行われているのでしょうか?
用語集には以下のような説明が続きます。

通常は反復可能オブジェクトを使う際には、 iter() を呼んだりイテレータオブジェクトを自分で操作する必要はありません。
for 文ではこの操作を自動的に行い、一時的な無名の変数を作成してループを回している間イテレータを保持します。

for文(や内包表記)は、イテラブルを組み込み関数iterに渡し、得られたイテレータを操作しているのです!

イテレータ

こちらも用語集を参照しましょう。
https://docs.python.org/ja/3/glossary.html#term-iterator

イテレータ__next__() メソッドを繰り返し呼び出す (または組み込み関数 next() に渡す) と、流れの中の要素を一つずつ返します。

イテレータは、そのイテレータオブジェクト自体を返す __iter__() メソッドを実装しなければならないので、イテレータは他の iterable を受理するほとんどの場所で利用できます。

イテレータはイテラブル

イテラブルiterablefor文に渡した時を考えてみましょう。

  1. iter(iterable)によりイテレータを取得
  2. 1のイテレータの要素を1つずつ取得(__next__

ではイテレータiteratorfor文に渡したときはどうでしょうか

  1. iter(iterator)iterator自身が返る(ref: 用語集のイテレータの項)
  2. iterator自身は__next__を実装しているので、要素を1つずつ取得できる

つまり、イテレータfor文で扱えるわけですね。
すなわち、イテレータはイテラブルです!3

なお、『Python実践入門』8.2にはイテラブルとイテレータについて、簡潔なまとめがあります。

コードで確認する「イテレータはイテラブル」

Python 3.10.9で動かしています

IteratorクラスはIterableクラスを継承しています4

>>> from collections.abc import Iterable, Iterator
>>> issubclass(Iterator, Iterable)
True

イテレータはイテラブル」をis-a関係が成り立つかで検証します(ref: next(ListはIteratorですか?))。
イテレータを継承したクラスを作り、そのインスタンスがイテラブルかどうかを確認します5

>>> class MyIterator(Iterator):
...   def __next__(self):
...     pass
...
>>> ite = MyIterator()
>>> isinstance(ite, Iterator)  # MyIteratorインスタンスはIterator
True
>>> isinstance(ite, Iterable)  # MyIteratorインスタンスはIterable!!
True

コレクション抽象基底クラスのドキュメントを参照した確認

IteratorIterableを継承するので、イテレータはイテラブルでした。
ここから、コレクション抽象基底クラスのドキュメントをもとにis-a関係を確認できます。

シーケンスはイテラブル

用語集によると、シーケンスはイテラブルでした。
ドキュメントを見ていくと

  • SequenceはCollectionを継承(Reversibleも継承)
  • CollectionはIterableを継承(SizedやContainerも継承)

つまり(AがBを継承している=class A(B)を A <- B のように表すと)

Sequence <- Collection <- Iterable

シーケンスの(少し離れた)親クラスはイテラブルなので、シーケンスはイテラブルですね。

マッピングはイテラブル

マッピングについても見てみましょう。

Mapping <- Collection <- Iterable

よって、マッピングはイテラブルです。

脱線「コンテナオブジェクト」

Python実践入門』ではコンテナオブジェクトが紹介されます。

リストや辞書、集合などのほかのオブジェクトへの参照を持つオブジェクトをコンテナオブジェクトと言います (Kindle の位置No.2188-2189 4.9)

コレクション抽象基底クラスのドキュメントによると、Containerは特殊メソッド__contains__が定義されたクラスです。
https://docs.python.org/ja/3/library/collections.abc.html#collections.abc.Container

__contains__() メソッドを提供するクラスの ABC です。

組み込み型のドキュメントのイテレータ型を参照するとcontainer.__iter__()が説明されています。
「ほかのオブジェクトへの参照を持つ」の根拠はこのあたりなのかなと考えています。

SequenceやMapping(さらにSet)はCollectionを継承しています。
そしてCollectionはContainerを継承しています。
なので、リストや辞書、集合はContainerと言えますね。

まとめ

P.S. ミノ駆動本_読書pyの予習 (終わりにに代えて)

3/24(金)開催、ミノ駆動本_読書pyの事前準備6の中で、「forで反復」「イテラブル」「イテレータ」を確認しました。

Pythonでファーストクラスコレクションの実装、白紙から考えてみるとどうするのがいいんだろう? 例えばイテラブルにするべきなのかな?」という疑問がきっかけです。
Pythonの用語の理解を深める中で「ファーストクラスコレクションを実装したいんだけど、それってシーケンスにしたいんだっけ?」といった視点に気づきました。

もう少し準備しますが、こういったトピックに興味があるよという方、ぜひ読書会でお会いしましょう!


  1. ダンダーメソッドとも言います。ref: https://docs.python.org/ja/3/glossary.html#term-special-method
  2. PyCon JP 2022のPythonとアスタリスクでも解説しました。
  3. 逆(イテラブルはイテレータ)は成り立ちませんね。反例としてはシーケンスはイテラブルですが、イテレータではありません
  4. https://docs.python.org/ja/3/library/collections.abc.html#collections-abstract-base-classesIteratorの行をご覧ください
  5. next(ListはIteratorですか?)のこちらのスライドです
  6. 読書ログ | #ミノ駆動本 7章「コレクション」の読書会予習に着手。Python関係のトピックがいくつか浮かびます! - nikkie-ftnextの日記