PythonのClassの生成に関してまとめてみた

TL;DR

Pythonのクラス、色々ありすぎて難しいのでまとめます。まずはClassの生成についてです。

Classの生成

  • まず、__new__メソッドが呼ばれ、その後__init__メソッドが呼ばれる。
  • __new__メソッド内では、クラス変数の定義などが行われる。__init__メソッド内では、インスタンス変数が定義できる。
  • クラス変数は、クラスから作成されたインスタンス全てが利用できる変数で、インスタンス変数はそれぞれのインスタンスからのみ利用できる変数。
  • クラス変数はインスタンスが生成されていなくても使えて、クラス変数を利用するメソッドには@classmethodのデコレータを付けて明示する。
  • 生成されたインスタンスはself、クラスそのものはclsと慣用的に命名される。

__new____init__の挙動確認

# -*- coding: utf-8 -*-
class Name:
    class_name = None # クラス変数

    def __new__(cls, name):
        print("new:", str(id(cls)))
        return super().__new__(cls)

    def __init__(self, name):
        print("==init==")
        print("id:", str(id(self)), "name:", name)
        self.name = name # インスタンス変数

    @classmethod
    def set_class_name(cls, class_name):
        cls.class_name = class_name

    @classmethod
    def print_class_name(cls):
        print(cls.class_name)

    @staticmethod
    def print_hi():
        print("hi!")

のようにクラスを定義します。@staticmethodはクラス変数にも生成されたインスタンス(cls, selfを引数に取らない)メソッドのデコレータで、このメソッドもインスタンスが生成されてなくても使えます。

以下の様に、@classmethod@staticmethodで定義されている部分については、インスタンスを生成しなくても使えていることがわかります。

Name.print_class_name()
Name.set_class_name("A")
Name.print_class_name()
Name.print_hi()

# None
# A
# hi!

次にインスタンスの生成過程を見てみます。

john = Name("John")
mike = Name("mike")

# new: 25652888
# ==init==
# id: 140347266309760 name: John

# new: 25652888
# ==init==
# id: 140347266309816 name: mike

インスタンスが生成された後のIDを見ると、__new__で生成されているIDは同じでクラス自身が生成されています。また、__init__で生成されているIDは異なっていて、違うインスタンスが生成されていることがわかります。

さらに、クラス変数を変更した場合の挙動を確認します。

print("classname:", john.class_name, "name:", john.name)
print("classname:", mike.class_name, "name:", mike.name)
print("-" * 10)
mike.set_class_name("B")
print("classname:", john.class_name, "name:", john.name)
print("classname:", mike.class_name, "name:", mike.name)

# classname: A name: John
# classname: A name: mike
# ----------
# classname: B name: John
# classname: B name: mike

どちらかの側でクラス変数を変更すると、両方のインスタンスでクラス変数が変更されていることがわかります。

__new__は何に使うのか

__new__の使用例としては、

  1. そのクラスが何回呼び出されたのかを記録する

  2. tupleなどのimmutableなオブジェクトの初期化

  3. メタクラスを利用してクラスを切り替える

などが挙げられます。公式的には、2,3が想定用途っぽいです (参考: 3. データモデル)。

__new__() の主な目的は、変更不能な型 (int, str, tuple など) のサブクラスでインスタンス生成をカスタマイズすることにあります。また、クラス生成をカスタマイズするために、カスタムのメタクラスでよくオーバーライドされます。

動的なクラス定義

typeobjectの引数1つを取った場合、object.__class__を返します。これによって、以下のようにクラスを判別することができます。

>>> n = 1
>>> type(n)
<class 'int'>
>>> n.__class__
<class 'int'>

クラスの生成のためには、name, bases, dictの3つの引数を取ります。これらの値は__name__, __bases__, __dict__に対応しています。__bases__は継承で、() or (object,)が最も基本的なクラスです。通常のクラス生成との対応は以下です。

def __init__(self, name):
    self.name = name

A = type('A', (), dict(__init__=__init__, a='1'))

# 上と同義
class A:
    a = '1'
    def __init__(self, name):
        self.name = name

print(A)
# <class '__main__.A'>

print(A("NAME").name)
# "NAME"

type(A)
# <class 'type'>

つまり、通常のクラス生成は__new__内でtypeのインスタンス化が行われていた、ということです。ここで重要なのは、typeclassです(参考: 組み込み関数)。この辺りの関係性はPythonのオブジェクトとクラスのビジュアルガイド – 全てがオブジェクトであるということなどが参考になります。

なので、typeを継承したクラスを使って__new__内でそのクラスを呼び出せば、クラスの生成を動的にカスタマイズできます。この概念がメタクラスです。

メタクラスの実例

メタクラスは「クラスのクラス」です。通常のクラスがインスタンスの振る舞いを定義するように、メタクラスはクラス自体の生成過程を制御します。typeを継承して__new__をオーバーライドすることで、独自のメタクラスを定義できます。

基本的なメタクラス — 属性の自動追加

最もシンプルな例として、クラス生成時に属性を自動で追加するメタクラスを定義してみます。

class AutoAttrMeta(type):
    def __new__(mcs, name, bases, namespace):
        # クラス生成時に自動で属性を追加
        namespace["created_by"] = "AutoAttrMeta"
        namespace["class_info"] = f"Class '{name}' was created by AutoAttrMeta"
        cls = super().__new__(mcs, name, bases, namespace)
        return cls

class MyClass(metaclass=AutoAttrMeta):
    pass

class AnotherClass(metaclass=AutoAttrMeta):
    value = 42

print(MyClass.created_by)
# AutoAttrMeta

print(MyClass.class_info)
# Class 'MyClass' was created by AutoAttrMeta

print(AnotherClass.created_by)
# AutoAttrMeta

print(AnotherClass.value)
# 42

metaclass=AutoAttrMetaと指定することで、class文の実行時にtype.__new__の代わりにAutoAttrMeta.__new__が呼ばれます。引数のmcsはメタクラス自身(AutoAttrMeta)、nameはクラス名、basesは基底クラスのタプル、namespaceはクラス本体で定義された属性の辞書です。

Registry パターン — クラスの自動登録

実務で最もよく使われるメタクラスのパターンの一つが、クラスの自動登録(Registry)です。サブクラスを定義するだけで自動的に辞書に登録され、プラグインシステムやシリアライゼーションの仕組みに活用できます。

class RegistryMeta(type):
    _registry = {}

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        # 基底クラス自体は登録しない
        if bases:
            mcs._registry[name] = cls
        return cls

    @classmethod
    def get_registry(mcs):
        return dict(mcs._registry)

class Serializer(metaclass=RegistryMeta):
    """基底クラス。これ自体は登録されない。"""
    def serialize(self, data):
        raise NotImplementedError

class JSONSerializer(Serializer):
    def serialize(self, data):
        return f"JSON: {data}"

class XMLSerializer(Serializer):
    def serialize(self, data):
        return f"XML: {data}"

class CSVSerializer(Serializer):
    def serialize(self, data):
        return f"CSV: {data}"

# サブクラスを定義しただけで自動的に登録される
print(RegistryMeta.get_registry())
# {'JSONSerializer': <class '__main__.JSONSerializer'>, 'XMLSerializer': <class '__main__.XMLSerializer'>, 'CSVSerializer': <class '__main__.CSVSerializer'>}

# 名前からクラスを取得してインスタンス化
serializer_name = "JSONSerializer"
serializer = RegistryMeta.get_registry()[serializer_name]()
print(serializer.serialize({"key": "value"}))
# JSON: {'key': 'value'}

このパターンでは、新しいサブクラスを定義するだけで自動的に登録されるため、登録漏れが起きません。プラグインシステムやコマンドディスパッチなど、拡張性が求められる設計で威力を発揮します。

Singleton パターン — インスタンス生成の制御

メタクラスの__call__をオーバーライドすると、インスタンスの生成過程を制御できます。__new__がクラスの生成を制御するのに対し、__call__はそのクラスが呼び出された時(= インスタンス生成時)の挙動を制御します。

class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            # 初回のみインスタンスを生成
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    def __init__(self, host="localhost"):
        self.host = host
        print(f"Database initialized: {host}")

class Logger(metaclass=SingletonMeta):
    def __init__(self, name="default"):
        self.name = name
        print(f"Logger initialized: {name}")

db1 = Database("production-server")
# Database initialized: production-server

db2 = Database("another-server")
# (何も出力されない。2回目の呼び出しでは__init__も実行されない)

print(db1 is db2)
# True

print(db1.host)
# production-server

# 異なるクラスは別々にシングルトン化される
logger = Logger("app")
# Logger initialized: app

print(db1 is logger)
# False

ここで重要なのは、メタクラスにおける__new____call__の役割の違いです。

  • __new__: class文が実行された時に呼ばれる。クラスオブジェクト自体を生成する
  • __call__: 生成済みのクラスがMyClass()のように呼び出された時に呼ばれる。インスタンスの生成を制御する

この区別は、記事の冒頭で説明した通常のクラスにおける__new__(インスタンスの生成)と__init__(インスタンスの初期化)の関係と似ています。メタクラスでは一段階上のレベルで同様の制御が行われているわけです。

まとめ

メタクラスは非常に強力な機能ですが、日常的なPythonプログラミングで必要になることは多くありません。Python 3.6以降では、__init_subclass__を使うことで、Registryパターンのようなユースケースをメタクラスなしで実現できます。

class Serializer:
    _registry = {}

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        Serializer._registry[cls.__name__] = cls

class JSONSerializer(Serializer):
    pass

print(Serializer._registry)
# {'JSONSerializer': <class '__main__.JSONSerializer'>}

メタクラスを使う前に、まずデコレータや__init_subclass__で目的が達成できないか検討するのが良いでしょう。それでも足りない場合に、メタクラスの出番です。

この記事に関するIssueをGithubで作成する

Read Next