まえがき
最近いろんな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 です。
曖昧な表現ですが、Sized
、Iterable
、Container
クラスを継承しているので、
**「コレクションとは、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
【最後の一言】
マジックメソッド奥が深いわーー