LoginSignup
9
7

More than 1 year has passed since last update.

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

Posted at

まえがき

最近いろんな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の基本データ型を持つクラスを作ります。

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演算子が使えるオブジェクト」になります。

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()メソッドが使えるオブジェクト」になります。

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()メソッドが使えるオブジェクト」になります。

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文で反復処理できますよね。。

公式の文言を引用すると、

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

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

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

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

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

abc.Sized

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

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

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 です。

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

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

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

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

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

abc.Sequence | シーケンス

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

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

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

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

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

【最後の一言】

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

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7