collections.abcから見るPythonデータ型の分類

## まえがき

最近いろんなPython本を読み漁っているのですが、
コンテナ（container）やシーケンス（sequence）などの定義が混乱してしまうのは私だけでしょうか？
備忘がてら整理してみようというのが今回のテーマです。

## TL;DR

| 型 | mutable? | container? | iterable? | iterator? | collection? | sequence? |
| -- | -- | -- | -- | -- | -- | -- |
| bool | × | × | × | × | × | × |
| int | × | × | × | × | × | × | 
| float | × | × | × | × | × | × |
| complex | × | × | × | × | × | × |
| str | × | ○ | ○ | × | ○ | ○ |
| tuple | × | ○ | ○ | × | ○ | ○ |
| bytes | × | × | ○ | × | × | ○ |
| frozenset | × | ○ | ○ | × | ○ | × |
| bytearray | ○ | × | ○ | × | × | ○ |
| list | ○ | ○ | ○ | × | ○ | ○ |
| set | ○ | ○ | ○ | × | ○ | × |
| dict | ○ | ○ | ○ | × | ○ | × |

## 検証

組み込みモジュールの中に色々な抽象基底クラスを定義する`collections.abc`があります。
その中に`Container`だの`Sequence`だのがありますよね。。
今回は、それらの定義を基にPythonの基本データ型を分類してみます。。


まずPythonの基本データ型を持つクラスを作ります。

```python
class PythonData:
    """Pythonのデータ型たち"""

    def __init__(self) -> None:
        # immutable data type
        self.bool_ = True
        self.int_ = 123
        self.float_ = 3.14
        self.complex_ = 3j
        self.str_ = "abc"
        self.tuple_ = ("a", "b", "c")
        self.bytes_ = b"ab\x63"
        self.frozenset_ = frozenset({"a", "b", "c"})
        # mutable data type
        self.bytearray_ = bytearray(range(0, 256))
        self.list_ = ["a", "b", "c"]
        self.set_ = {"a", "b", "c"}
        self.dict_ = {"foo": "bar"}
```

さぁ地道に検証していきましょう。。

### `abc.Container` | コンテナ

> \_\_contains\_\_() メソッドを提供するクラスの ABC です

とあるので、、

**「コンテナとは、`in`演算子が使えるオブジェクト」**になります。

```python
def is_container(data: PythonData) -> None:
    """containerとは?: in演算子が使える"""

    for type in data.__dict__:
        try:
            # 判定結果は気にせず呼び出せればよし
            if "a" in data.__dict__[type]:
                print(f"* {type} is container.")
            else:
                print(f"* {type} is container.")
        except TypeError:
            pass

is_container(PythonData())

# * str_ is container.
# * tuple_ is container.
# * frozenset_ is container.
# * list_ is container.
# * set_ is container.
# * dict_ is container.
```

【検証結果】

**文字列、タプル、リスト、集合、辞書、frozensetはコンテナである。**


### `abc.Iterable` | イテラブル

> \_\_iter\_\_() メソッドを提供するクラスの ABC です。

とあるので、、

**「イテラブルとは、`iter()`メソッドが使えるオブジェクト」**になります。

```python
def is_iterable(data: PythonData) -> None:
    """iterableとは？: iter()を呼べる"""

    for type in data.__dict__:
        try:
            iter(data.__dict__[type])
            print(f"* {type} is iterable.")
        except TypeError:
            pass

is_iterable(PythonData())

# * str_ is iterable.
# * tuple_ is iterable.
# * bytes_ is iterable.
# * frozenset_ is iterable.
# * bytearray_ is iterable.
# * list_ is iterable.
# * set_ is iterable.
# * dict_ is iterable.
```

【検証結果】

**文字列、バイト、バイト配列、タプル、リスト、集合、辞書、frozensetはイテラブルである**


### `abc.Iterator` | イテレータ

`abc.Iterable`クラスを継承しています。

> \_\_iter\_\_() メソッドと \_\_next\_\_() メソッドを提供するクラスの ABC です。

とあるので、、

**「イテレータとは、`iter()`、`next()`メソッドが使えるオブジェクト」**になります。

```python
def is_iterator(data: PythonData) -> None:
    """iteratorとは?: next()を呼べる"""

    for type in data.__dict__:
        try:
            next(data.__dict__[type])
            print(f"* {type} is iterator.")
        except TypeError:
            pass

is_iterator(data)
# 該当なし
```

【検証結果】

**該当なし**

【考察】

やや想定外の結果ですね。。
Pythonのイテラブルなデータ型は全て**「イテレータではない」**ということになります。

でも、リストもタプルも`for`文で反復処理できますよね。。

[公式](https://docs.python.org/ja/3/glossary.html#term-iterator)の文言を引用すると、

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

とありますので、
`for`文は内部的に`iter()`メソッドを呼び、イテラブルからイテレータを生成している、
ということになります。

以下は、`for`文の内部処理のイメージです（大体合っているハズ）

```python
def my_for(iterable):
    iter_ = iter(iterable)
    while True:
        try:
            next(iter_)
        except StopIteration:
            break
```

であれば、**「該当なし」**の結果も頷けますね。
リストやタプルが`for`文で反復処理できることは自明なのでそこは割愛します。

### `abc.Sized`

本編の趣旨からは若干外れますが、後続の説明のためです。

> \_\_len\_\_() メソッドを提供するクラスの ABC です。

```python
def is_sized(data: PythonData) -> None:
    """sizedとは?: len()を呼べる"""

    for type in data.__dict__:
        try:
            len(data.__dict__[type])
            print(f"* {type} is collection.")
        except TypeError:
            pass


is_sized(PythonData())

# * str_ is collection.
# * tuple_ is collection.
# * bytes_ is collection.
# * frozenset_ is collection.
# * bytearray_ is collection.
# * list_ is collection.
# * set_ is collection.
# * dict_ is collection.
```

### `abc.Collection` | コレクション

> サイズ付きのイテラブルなコンテナクラスの ABC です。

曖昧な表現ですが、`Sized`、`Iterable`、`Container`クラスを継承しているので、

**「コレクションとは、`len()`、`iter()`、`in`演算子が使えるオブジェクト」**になります。

これまでの結果の積集合を取れば良いので結果は、

**文字列、タプル、リスト、集合、辞書、frozensetはコレクションである。**

コンテナと同じ結果なので、上記は**「コンテナかつコレクション」**とも言えます。

### `abc.Sequence` | シーケンス

こちらはシーケンス自体の定義になります。

> (シーケンス) 整数インデクスによる効率的な要素アクセスを \_\_getitem\_\_() 特殊メソッドを通じてサポートし、長さを返す \_\_len\_\_() メソッドを定義した iterable です。

また`Collection`を継承しているので、

**「シーケンスとは、整数インデックスで要素が参照できるオブジェクト」**になります。

```python
def is_sequence(data: PythonData):
    """sequenceとは?: インデックスによる要素アクセスができる"""

    for type in data.__dict__:
        try:
            data.__dict__[type][0]
            print(f"* {type} is sequence.")
        except TypeError:
            pass
        except KeyError:
            pass

is_sequence(PythonData())

# * str_ is sequence.
# * tuple_ is sequence.
# * bytes_ is sequence.
# * bytearray_ is sequence.
# * list_ is sequence.
```

【検証結果】

**文字列、バイト、バイト配列、タプル、リストはシーケンスである。**


## まとめ

まとめたものが冒頭のマトリクスになります。
複素数(complex)とかいらんかったわーって後から思いましたw

検証ベースなので、本当の定義とは異なる可能性はあります。。
何卒ご了承くださいm(_ _)m

`sequence`とかは`__getitem__`に依存しているので文字列のインデックス（つまりキー）で
参照できるようにすれば辞書（`abc.Mapping`）になるんだー
とか、
`__setitem__`と`__delitem__`を実装すればミュータブルなオブジェクトになるんだー

とか違った気付きもあって良かったですw

【最後の一言】

マジックメソッド奥が深いわーー