はじめに
京都リサーチパークにて開催のYAPC::Kyotoでノベルティを受け取りし参加者の1人、nikkieです。
Pythonはクラスに特殊メソッド1を定義することで、インスタンスに特別な振る舞いをさせることができます。
今回は、インスタンスをfor文で使えるようにする実装まわりの自分の理解を整理します。
目次
- はじめに
- 目次
- イテラブル
- イテラブルを反復するとき
- イテレータ?
- イテレータはイテラブル
- コレクション抽象基底クラスのドキュメントを参照した確認
- 脱線「コンテナオブジェクト」
- まとめ
- P.S. ミノ駆動本_読書pyの予習 (終わりにに代えて)
イテラブル
「反復可能オブジェクト」とも呼ばれます。
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 を受理するほとんどの場所で利用できます。
イテレータはイテラブル
イテラブルiterableをfor文に渡した時を考えてみましょう。
ではイテレータiteratorをfor文に渡したときはどうでしょうか
つまり、イテレータも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
コレクション抽象基底クラスのドキュメントを参照した確認
IteratorはIterableを継承するので、イテレータはイテラブルでした。
ここから、コレクション抽象基底クラスのドキュメントをもとに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と言えますね。
まとめ
- Pythonの
for文や内包表記は、イテラブルから得られたイテレータを操作している - イテレータはイテラブル
- イテレータ自身も
for文や内包表記に渡せる
- イテレータ自身も
- AはB(=AがBを継承している)はコレクション抽象基底クラスのドキュメントで確認できる
- シーケンスはイテラブル
- マッピングはイテラブル
P.S. ミノ駆動本_読書pyの予習 (終わりにに代えて)
3/24(金)開催、ミノ駆動本_読書pyの事前準備6の中で、「forで反復」「イテラブル」「イテレータ」を確認しました。
「Pythonでファーストクラスコレクションの実装、白紙から考えてみるとどうするのがいいんだろう? 例えばイテラブルにするべきなのかな?」という疑問がきっかけです。
Pythonの用語の理解を深める中で「ファーストクラスコレクションを実装したいんだけど、それってシーケンスにしたいんだっけ?」といった視点に気づきました。
もう少し準備しますが、こういったトピックに興味があるよという方、ぜひ読書会でお会いしましょう!
- ダンダーメソッドとも言います。ref: https://docs.python.org/ja/3/glossary.html#term-special-method↩
- PyCon JP 2022のPythonとアスタリスクでも解説しました。 ↩
- 逆(イテラブルはイテレータ)は成り立ちませんね。反例としてはシーケンスはイテラブルですが、イテレータではありません↩
- https://docs.python.org/ja/3/library/collections.abc.html#collections-abstract-base-classes のIteratorの行をご覧ください↩
- next(ListはIteratorですか?)のこちらのスライドです↩
- 読書ログ | #ミノ駆動本 7章「コレクション」の読書会予習に着手。Python関係のトピックがいくつか浮かびます! - nikkie-ftnextの日記↩
