Python の iterable ってなに?




for 文の in の中に代入できるもの







例えば list, tuple, str, dict は iterable なオブジェクトです。

iterable = [0, 1, 2]
for element in iterable:
    ...

iterable は for ループの中で ... で使われます。
Iterables can be used in a for loop ...
iterable - Glossary



簡単には、次の関数で iterable であるかどうか判定できます。

def isiterable(container):
    return hasattr(container, '__iter__')


iterable な組込型の一覧を取得しました。

import builtins

builtin_types = [
    t for t in builtins.__dict__.values() if isinstance(t, type)]

builtin_iterbale_types = [
    t for t in builtin_types if hasattr(t, '__iter__')]

for t in builtin_iterbale_types:
    print(t)

# <class 'bytearray'>
# <class 'bytes'>
# <class 'dict'>
# <class 'enumerate'>
# <class 'filter'>
# <class 'frozenset'>
# <class 'list'>
# <class 'map'>
# <class 'range'>
# <class 'reversed'>
# <class 'set'>
# <class 'str'>
# <class 'tuple'>
# <class 'zip'>







次の記事からの続きです。

1. iterable であるかどうか調べたい。

# iterable であるかどうかを判定するコード
def isiterable(container):
    if hasattr(container, '__iter__'):
        return True
    elif hasattr(container, '__getitem__'):
        raise Exception
    else:
        return False
# iterable であるかどうかをテストするコード
#   実際に実行してしまうしかない
#   -> 副作用を伴わずに正確に判定することはできない。
def assertIsIterable(container, iterable):
    assert all(a == b for a,b in zip(container, iterable))

0. どういうオブジェクトなら in の中で使うことができるの?


  1. __iter__ を実装したクラスに属するオブジェクト
  2. __geitem__ をシーケンスとして実装したクラスに属するオブジェクト

あるいはユーザが __iter__ メソッドもしくはシーケンスの動作をする __getitem__ メソッドを実装した全てのクラスです
, and objects of any classes you define with an __iter__() method or with a __getitem__() method that implements Sequence semantics.
iterable - Glossary

1. __iter__ があれば iterable と言っていいの?

答え: 言っていいです。

Python では __iter__ は全てのオブジェクトが iterator を返す様にマニュアルで定められているからです。

container.__iter__()
イテレータオブジェクトを返します。


前後左右に2つのアンダースコアで挟まれた変数、または属性は __*__ 、マニュアルで記載された以外の用途で使ってはならないことになっています。

例えば __init__ メソッドを初期化以外の別の用途で使ったら、大変なことになってしまいますよね。

このドキュメントで明記されている用法に従わない、 あらゆる __*__ の名前は、いかなる文脈における利用でも、警告無く損害を引き起こすことがあります。
2.3.2. 予約済みの識別子 - Python 言語リファレンス

2. __getitem__ があると判定できないの?

答え: 難しいと思います。

シーケンスかマッピングか区別することができません。 シーケンスは seq[0], seq[1] と数字で参照できるもの。例えば list, tuple, str がそれに当たります。 マッピングは mpg['a'], mpg['b'] と数字以外でも参照できるもの。例えば dict がそれに当たります。

Guido もできないと言っています。そのため例外を投げています。

しかし、もし、クラスインスタンスなら、最善の方法は __getitem__ を定義しているかどうかを確認し、オブジェクトが辞書でないことを望むしかありません!
but if it is a class instance, the best you can do is check whether it defines __getitem__ and hope it isn't a dictionary!
なぜイテレータは __iter__ メソッドを持たないといけないのか
Why must an iterator have an __iter__ method?

クラスインスタンス (class instance)
クラスインスタンスは、クラスオブジェクト (上記参照) を呼び出して生成します。 クラスインスタンスは辞書で実装された名前空間を持っており、属性参照の時にはまずこの辞書が探索されます。
3.2. 標準型の階層 - Python 言語リファレンス

2. duck typing

__iter__ メソッドさえ定義されていれば iterable だと言えます。

Java とは違い Python では、例えば iterable という interface が継承されていなくても、 同じ名前の method さえ定義されていれば、iterable という interface を実装している様に振舞うことができます。 この様な型付の性質は duck typing と呼ばれたりします。

iterable という interface が宣言されていなくても(duck というinterface がされていなくとも)、 iter メソッドを実装していて、そいつが iterator のように振る舞うなら(duck のように鳴き、よちよち歩くなら)、 そいつは iterator だ!(duck だ!)という意味合いだそうです。

static typing が静的型付け, dynamic typing が動的型付けと訳されるなら、 duck typing は鴨的型付けって訳になるんですかね..。
デザインパターン「Iterator」-Qiita (Java での iterator の例)

3. static duck typing

クラス定義時に __iter__ メソッドさえ定義されていれば iterable だと判定することを static duck typing(structural subtyping) と言います。 なんのこっちゃ?って感じですね。

例えば、昔の mypy では __iter__ を実装しているだけでは iterable と判定してくれず、for 文でエラーを返してきました。

class Foo(object):
    def __iter__(self):
        yield 1
        yield 2

for x in Foo():
    print(x)
$ # Foo() が iterable ではないと返される
$ mypy sample.py 
sample.py:6: error: Iterable expected
$
$ mypy -V
mypy 0.540
$


どのようにすればいいのかと言うと typing.Iterable を継承する必要がありました。 このように明示的に型を指定し、それを元に判定することを nominal subtyping と PEP 544 では表現されています。

from typing import Iterator, Iterable

class Foo(Iterable):
    def __iter__(self) -> Iterator[int]:
        yield 1
        yield 2

for x in Foo():
    print(x)
$ mypy sample.py 
$
$ mypy -V
mypy 0.540
$

Mypy doesn't recognize objects implementing __iter__ as being Iterable · Issue #2598 · python/mypy · GitHub


いまの mypy では、static duck typing できるようになっています。

class Foo(object):
    def __iter__(self):
        yield 1
        yield 2

for x in Foo():
    print(x)
$ mypy sample.py 
$
$ mypy -V
mypy 0.610
$


structural subtyping については PEP 544 にて議論されています。
PEP 544 - Protocols: Structural subtyping (static duck typing)

4. 用語

基本形, 派生型, 公称型, 構造型という4つの言葉を紹介します。

# 1. 基本形 ... 親クラス
class Iterable:
    def __iter__(self):
        raise NotImplemented


# 2. 派生型 ... 子クラス
# 2.1. 公称型 ... 明示的に親クラスを継承した子クラス
class IteratorA(Iterable):
    def __iter__(self):
        return self
    
    def __next__(self):
        raise StopIteration

# 2.2. 構造型 ... 明示的に親クラスを継承していないが、
#                 親クラスが持つメソッドを実装した子クラス
class IteratorB:
    def __iter__(self):
        return self
    
    def __next__(self):
        raise StopIteration

コンピュータサイエンスにおいて、データ型S が他のデータ型T とis-a関係にあるとき、 S をT の 派生型(はせいがた、subtype)であるという。またT はS の 基本型(きほんがた、supertype)であるという。

...

型理論の研究者は、派生型であると宣言されたもののみを派生型とする nominal subtyping(nominative; 公称型)と、 2つの型の構造によって派生型関係にあるかが決まる structural subtyping(structural; 構造型)を区別する。
派生型 - Wikipedia

5. iterable

iterable - Glossary
一度に一つずつ、自分が持つ要素を返すことができるオブジェクト。iterable の例には、次の型に属するオブジェクトが含まれます。まず list, str, tuple などの全てのシーケンス型や、また dict, file object などのシーケンスでない型、あるいはユーザが __iter__ メソッドもしくはシーケンスの動作をする __getitem__ メソッドを実装した全てのクラスです(ワイの注釈: 昔の Python は組込型を type, ユーザ定義型を class と表現していました。型, クラスという言葉がでてきているのは、そのためです。)
An object capable of returning its members one at a time. Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict, file objects, and objects of any classes you define with an __iter__() method or with a __getitem__() method that implements Sequence semantics.

iterable は for ループの中で、また他の場所ではシーケンスが必要とされる場所(zip, map 関数の引数として...)で使われます。iterable オブジェクトが組込関数の iter に実引数として渡された時、iter 関数は iterable なオブジェクトのイテレータを返します。このイテレータは、iterable なオブジェクトが持つ値の集合を1つ1つ辿る処理に適しています。iterable なオブジェクトを使う時、必ずしも iter 関数を呼び出したり、もしくはイテレータオブジェクトそのものを取り扱う必要はありません。for 文は、プログラマのために自動的にそう言ったことを実行してくれます、for ループの間、イテレータを保持するための名前のない一時変数を生成します。iterator, sequence そして generator の項も参照してください。
Iterables can be used in a for loop and in many other places where a sequence is needed (zip(), map(), …). When an iterable object is passed as an argument to the built-in function iter(), it returns an iterator for the object. This iterator is good for one pass over the set of values. When using iterables, it is usually not necessary to call iter() or deal with iterator objects yourself. The for statement does that automatically for you, creating a temporary unnamed variable to hold the iterator for the duration of the loop. See also iterator, sequence, and generator.


see also...
What exactly are iterator, iterable, and iteration? - Stack Overflow

5. おわりに

イテレータからはじめて、かなり長々と書き、最終的に structural subtyping にまでたどり着いてしまいました。 しかしイテレータPython にとって for 文そのものであり、もっともよく使われている機能です。 これを掘り下げて理解することは、そこまで悪いことではないのかなと思ったりもします。