このBlogは移転しました。今後は aish.dev を御覧ください。

Python 3.6の概要 (その4 - クラス定義)

クラス定義のカスタマイズ

これまで、Pythonのクラス定義をカスタマイズする手段として、メタクラスが使われてきた。しかし、メタクラスを利用したカスタマイズは、Pythonのオブジェクトモデルや型システムの知識が必要で実装が難しく、また複数のメタクラスを同時に使用するのが難しい、などの問題点があった。そこで、PEP 487 -- Simpler customisation of class creation では、メタクラスを使わずにクラスをカスタマイズする手段を提供している

init_subclass() メソッド

クラスのサブクラスが作成されたときに呼び出され、引数として、派生クラスと、クラス定義の引数が渡される。__init_subclass__()メソッドは、自動的にクラスメソッドとなる。

class Spam:
    def __init_subclass__(cls, **kwargs):
         super().__init_subclass__()
         print('Spam:', cls, kwargs)

class Ham(Spam, bacon=1):
    def __init_subclass__(cls, **kwargs):
         super().__init_subclass__(**kwargs)
         print('Ham:', cls, kwargs)

class Egg(Ham, cheese=1):
    def __init_subclass__(cls, **kwargs):
         super().__init_subclass__(**kwargs)
         print('Egg:', cls, kwargs)

# prints
# Spam: <class '__main__.Ham'> {'bacon': 1}
# Spam: <class '__main__.Egg'> {'cheese': 1}
# Ham: <class '__main__.Egg'> {'cheese': 1}

ディスクリプタset_name()メソッド

従来のディスクリプタインターフェース__set_name__() メソッドが追加された。__set_name__()はクラスオブジェクトの作成時に呼び出され、引数としてクラスと属性名を受け取る。

class Descr:
    def __get__(self, instance, owner):
        return f'Descr.__get__({instance}, {owner})'

    def __set_name__(self, owner, name):
        print(f'Descr.__set_name__({owner}, {name})')

class Spam:
    descr = Descr()

のように、クラス Spamディスクリプタ Descrインスタンスを属性として作成すると、クラス Spam 作成時に Descr.__set_name__(Spam, 'descr') が呼び出される。

属性の作成順

メタクラスの一般的な使い方として、クラスの属性を、作成された順番にしたがって処理する、というケースがある。この場合、メタクラスでクラスの名前空間に使用する辞書として、collections.OrderedDict()を使用するようにカスタマイズするのが一般的だが、Python 3.6からは名前空間辞書はデフォルトで登録順序を保存するようになったため、このようなカスタマイズは不要となった。

次のように、クラスの名前空間辞書 __dict__ の要素を取得すると、常に属性が登録された順番で返される。

>>> class Spam:
...     a=1
...     b=2
...     c=3
...
>>> Spam.__dict__.keys()  # 常に同じ順番で取得できる  
dict_keys(['__module__', 'a', 'b', 'c', '__dict__', '__weakref__', '__doc__'])