Jaybanuan's Blog

どうせまた調べるハメになることをメモしていくブログ

Python 3のコレクションの抽象基底クラス

時々、dict風の動作をする自前クラスを作りたくなることがあるが、何を満たせばdict風になるのか毎回調べていたので、ここにメモしておく。 と言っても、ドキュメントに全てまとまっているので、以下を参照するだけ。

docs.python.org

抽象基底クラスについては、PEP 3119で提案がある。

peps.python.org

ここで、以下のサンプルコードを実行してdictMutableMappingのクラス階層を表示してみる。

import collections.abc

from typing import *


def print_class_hierarchy(t: Type, depth: int = 0):
    print(''.join('    ' for _ in range(depth)) + t.__name__ + ' (' + t.__class__.__name__ + ')')

    if t != object:
        for base_type in t.__bases__:
            print_class_hierarchy(base_type, depth+1)


print_class_hierarchy(dict)
print()
print_class_hierarchy(collections.abc.MutableMapping)
print()
print(issubclass(dict, collections.abc.MutableMapping))

出力結果は以下のようになる。

dict (type)
    object (type)

MutableMapping (ABCMeta)
    Mapping (ABCMeta)
        Collection (ABCMeta)
            Sized (ABCMeta)
                object (type)
            Iterable (ABCMeta)
                object (type)
            Container (ABCMeta)
                object (type)

True

出力結果より、dictMutableMappingはクラス階層的には継承関係にはないことが読み取れるが、issubclass(dict, MutableMapping)Trueになる。 これは、PEP 3119の提案によると、issubclass()をオーバーライドして、あたかもdictMutableMappingのサブクラスであるかのように見せかけているためである。 言語のコアな部分を変更せずに、コレクションの性質を抽象化する工夫と思われる。

最後に蛇足だが、もうちょっと広くPythonのデータモデル (というより型モデル) はこちらを参照。

docs.python.org

2013/10/15追記。 各クラスのメソッドも表示するようにして、継承関係のツリーの罫線も引いてみた。

import inspect
import collections.abc

from typing import *


def print_class_hierarchy(node: Union[Type, str], indent_types: list[str] = []) -> None:
    indents = ''
    for index, indent_type in enumerate(indent_types):
        indent = '??????'   # this value must be never used
        if index != len(indent_types) - 1:
            if indent_type == 'TYPE':
                indent = '  |   '
            elif indent_type == 'TYPE_LAST':
                indent = '      '
        else:
            if indent_type == 'METHOD':
                indent = '  |   '
            elif indent_type == 'METHOD_LAST':
                indent = '      '
            elif indent_type == 'TYPE':
                indent = '  +-- '
            elif indent_type == 'TYPE_LAST':
                indent = '  `-- '
        
        indents = indents + indent

    node_text = (node.__name__ + ' (' + node.__class__.__name__ + ')') if isinstance(node, Type) else node
    print(indents + node_text)

    if isinstance(node, Type):
        base_types = list(node.__bases__)

        methods = {member for member, _ in inspect.getmembers(node, inspect.isfunction)}
        base_methods = {member for base_class in node.__bases__ for member, _ in inspect.getmembers(base_class, inspect.isfunction)}
        indent_type = 'METHOD' if len(base_types) > 0 else 'METHOD_LAST'

        for method in list(methods - base_methods):
            print_class_hierarchy(method, indent_types + [indent_type])

        if node != object:
            for index, base_type in enumerate(base_types):
                indent_type = 'TYPE' if index != len(base_types) - 1 else 'TYPE_LAST'
                print_class_hierarchy(base_type, indent_types + [indent_type])


print_class_hierarchy(collections.abc.MutableMapping)

出力結果は以下。

MutableMapping (ABCMeta)
  |   setdefault
  |   __setitem__
  |   pop
  |   clear
  |   popitem
  |   __delitem__
  |   update
  `-- Mapping (ABCMeta)
        |   items
        |   get
        |   values
        |   keys
        |   __eq__
        |   __getitem__
        `-- Collection (ABCMeta)
              +-- Sized (ABCMeta)
              |     |   __len__
              |     `-- object (type)
              +-- Iterable (ABCMeta)
              |     |   __iter__
              |     `-- object (type)
              `-- Container (ABCMeta)
                    |   __contains__
                    `-- object (type)