メタクラス - クラス生成をカスタマイズする

【 目次 】

他のサイトに優れた解説記事があるのでまずはこれを参照。

英語ではあるが

次の記事も参考になる。

公式サイトの記事も


これらの記事を踏まえた上で、クラス生成のカスタマイズについて整理してみよう。

type関数の結果を表示してみる。

組込み関数typeによりオブジェクトの型を調べる事ができる。
type関数を使って、クラスについて復習してみよう。

以下のようなクラスとクラスのインスタンスがあったとする。

class MyCls(object):
    pass

my_inst=MyCls()

type関数を使ってクラスMyClsのインスタンスの型を表示してみる。

print type(my_inst)

実行結果

<class '__main__.MyCls'>

つまりクラスMyClsのインスタンスの型はMyCls型であり、

そしてクラスMyClsの型は

print type(MyCls)

実行結果

<type 'type'>

type型という事になる。

ではtype型の型はというと

print type(type)
<type 'type'>

type型自分自身となる。

組込み型の場合はどうであろうか?

文字列型(str型)のオブジェクト(インスタンス)を定義して、type関数の結果を表示してみる。

str_obj=str("abc")  # または inst_str="abc" とも記述できる。
print type(str_obj),type(str)

実行結果

<type 'str'> <type 'type'>

int型の場合はどうであろうか?

int_objrct=int(3)  # または int_objrct=3 とも記述できる。
print type(int_objrct),type(int)

実行結果

<type 'int'> <type 'type'>

つまり、組込み型(クラス)の型もtype型という事になる。

特殊な例として関数の型についても調べてみる。

def func():
    pass
print type(func)

実行結果

<type 'function'>

つまり、def構文による関数の定義は、functionクラスのインスタンスを定義していると解釈する事もできる。

ここまでは、python2での結果であるが、python3についても確認してみる。
python3ではprint文が組込み関数に変更されているので、カッコを付加して

print(type(my_inst))
print(type(MyCls))
print(type(type))
print(type(str_obj),type(str))
print(type(int_objrct),type(int))
print(type(func))

実行結果

<class '__main__.MyCls'>
<class 'type'>
<class 'type'>
<class 'str'> <class 'type'>
<class 'int'> <class 'type'>
<class 'function'>

python2と比較すると

python2の場合、type(my_inst)の結果だけが<class '__main__.MyCls'>と表示されているのに対して、他の場合はすべて<type '型名'>と表示されている。
ユーザ定義型(クラス)の場合にはclass,組込み型の場合にはtypeと区別しているようである。

ところがpython3の場合、すべて<class '型名'>と表示されている。
python3ではユーザ定義の型と組込み型の区別がより少なくなってきているのではないかと推察される。

メタクラスとは

まずは、クラスとインスタンスが何を指すのか不明確なので、

  • インスタンス - Wikipedia

    静的型付けのオブジェクト指向言語では珍しいが、動的型付けのオブジェクト指向言語の多くは、メタクラスをサポートし、クラス自体もオブジェクトとして扱うことができる(クラス・オブジェクト)。クラス・オブジェクトは、端的に言えば変数に束縛できるクラスである。クラス・オブジェクト、インスタンス・オブジェクト双方を変数に束縛した際どちらもオブジェクトとして振る舞い見かけ上区別はつかない。例えばクラス・オブジェクト、インスタンス・オブジェクト双方が readFrom: というメソッドを持っていた場合、どちらも #readFrom: メッセージを送ってやるとエラーも起こさずそれぞれのメソッドを実行する。

    Objective-CやPythonにおいてはクラス・オブジェクトとインスタンス・オブジェクトの明確な区別が行われている。

    メタクラスがサポートされているシステムでは、クラス・オブジェクトもまた別のクラス(メタクラス)のインスタンスであるということがありうる。この場合「クラス・オブジェクトはインスタンスではない」とは言えないので、注意されたい。

type関数の表示結果をみてみると、ユーザ定義の型(クラス),組込み型にかかわらす、すべての型の型はtype型になっている。

pythonのすべてのものはオブジェクトである。

クラスのインスタンスはインスタンスオブジェクト。 クラスもオブジェクトでありクラスオブジェクトと呼ばれる。
すなわち、クラスも実はtypeクラスのインスタンスなのである。

このtypeクラスのように、クラスのもととなるクラスの事をメタクラスと言う。
メタクラスにより後述するようにクラスの生成をカスタマイズする事ができる。

メタの意味は

  • メタ - Wikipedia

    メタ(meta-)とは、「高次な-」「超-」「-間の」「-を含んだ」「-の後ろの」等の意味の接頭語。ギリシア語から。
    ...
    ある対象を記述したものがあり、さらにそれを対象として記述するものを、メタな○○、あるいは単にメタ○○と呼ぶ。
    ...
    広義に、何かを取り込んだ何か、何かについての何か、といったものがメタと呼ばれる場合がある。

そしてメタクラスとはクラスのクラスという事になる。

  • メタクラス - Wikipedia

    オブジェクト指向プログラミングにおいてメタクラスとは、インスタンスがクラスとなるクラスのことである。通常のクラスがそのインスタンスの振る舞いを定義するように、メタクラスはそのインスタンスであるクラスを、そして更にそのクラスのインスタンスの振る舞いを定義する。

誤解しやすい点であるが、クラスとメタクラスの関係は、継承関係にあるのではなく(クラスがメタクラスのサブクラスの関係にあるのではなく)、クラスがメタクラスのインスタンスという関係にあるという事であるのでこの点に注意。

type関数により動的にクラスを生成

クラスもメタクラスtypeのインスタンスである事は既に述べた。
という事は、メタクラスtypeのインスタンスであるクラスを生成する場合にも、通常のインスタンスと同じような方法で生成すれば良い事になる。

class MyCls(object):
    pass

クラスのインスタンスの生成は通常、クラスを関数のように呼び出して、

my_inst=MyClass()

と記述する。

同様に、クラスもtypeクラスを関数のように呼び出して生成する事ができる。
この時、type関数には3つの引数をとる組込み関数type(name, bases, dict)を使う事になる。

引数nameはクラス名となるクラスの特殊属性__name__の値となる。
basesには基底クラスをタプルで指定する。
この値は特殊属性__bases__の値となる。
dictにはクラスの属性を辞書として記述する。
この値は特殊属性__dict__の値となる。

type関数を使って動的にクラス生成してみよう。

以下のような2つクラスを基底クラスとして多重継承をおこなうクラスの例について考えてみる。

class SuperCls1(object):
    pass

class SuperCls2(object):
    pass

class MyCls(SuperCls1,SuperCls2):
    c_var = u"クラス変数"

これと等価なクラスをtype関数を使って生成(定義)してみると。

MyCls=type("MyCls",(SuperCls1,SuperCls2),{"c_var":u"クラス変数" })

クラスのメンバーを確認してみる。

print MyCls.__name__
print MyCls.__bases__
print MyCls.__dict__

実行結果

MyCls
(<class '__main__.SuperCls1'>, <class '__main__.SuperCls2'>)
{'c_var': u'\u30af\u30e9\u30b9\u5909\u6570', '__module__': '__main__', '__doc__': None}

MyCls.__dict__にはクラスのデフォルトのの属性(多分これはtypeクラスで定義されているのであろう)にdict引数の要素が追加されているのがわかる。

少し複雑なclassの定義の場合はどうだろう。

class MyCls:
    c_var = u"クラス変数"

    # コンストラクタ
    def __init__(self):
        self.i_var = u"インスタンス変数"

    # インスタンスメソッド
    def i_method(self):
        print u"インスタンスメソッドを実行しました"

    # クラスメソッド
    @classmethod
    def c_method(cls):
        print u"クラスメソッドを実行しました"

    # スタティックメソッド
    @staticmethod
    def s_method():
        print u"スタティックメソッドを実行しました"

上記のclass文によるクラス定義と等価のクラスをtype関数を使って生成してみる。

# コンストラクタのもととなる関数
def init_func(self):
    self.i_var = u"インスタンス変数"

# インスタンスメソッドのもととなる関数
def i_func(self):
    print u"インスタンスメソッドを実行しました"
    print "self.i_var=", self.i_var

# クラスメソッドのもととなる関数
def c_func(cls):
    print u"クラスメソッドを実行しました"
    print "cls.c_var=", cls.c_var

# スタティックメソッドのもととなる関数
def s_func():
    print u"スタティックメソッドを実行しました"

# クラスオブジェクトの生成
MyCls = type('MyCls', (object,), 
             dict(
                     c_var=u"クラス変数",
                     __init__=init_func,
                     i_method=i_func,
                     c_method=classmethod(c_func),
                     s_method=staticmethod(s_func),
                 )
             )

メソッドはクラスの属性になるので、dict引数の要素として外部で定義している関数を渡している。
関数をそのまま、クラスの属性に指定するとインスタンスメソッドとして認識されてしまうので、
クラスメソッドやスタティックメソッドは組込み関数classmethodやstaticmethodによる変換が必要になる。(「関数をメソッドに動的にバインドする(結びつける)。」も参照。)

クラスメソッドやインスタンスメソッドを実行して動作を確認してみよう。

MyCls.c_method()
MyCls.s_method()

my_inst=MyCls()
my_inst.i_method()

実行結果

クラスメソッドを実行しました
cls.c_var= クラス変数
スタティックメソッドを実行しました
インスタンスメソッドを実行しました
self.i_var= インスタンス変数

期待どうりに動作しているのが確認できる。

クラス生成のメカニズム

クラス生成のメカニズムについて考えてみよう。
クラスは生成時には2段階のクラスの特殊メソッドが呼び出される。

はじめに、特殊メソッド__new__が呼び出されクラスのインスタンスが生成される。 この段階ではインスタンスの初期化はまだおこなわれていない。

  • __new__メソッド - データモデル — Python 2.7ja1 documentation

    __new__() は静的メソッドで (このメソッドは特別扱いされているので、明示的に静的メソッドと宣言する必要はありません)、インスタンスを生成するよう要求されているクラスを第一引数にとります。残りの引数はオブジェクトのコンストラクタの式 (クラスの呼び出し文)に渡されます。

__new__メソッドは、明示的にデコレータ@classmethodを指定する必要は無いが実はクラスメソッドである。

__new__メソッドの内部では、クラスのインスタンスを生成して返す。

次に特殊メソッド__init__が呼ばれ__init__の処理によって__new__メソッドによって生成されたインスタンスに対して初期化がおこなわれる。

メタクラスの指定 - __metaclass__

クラスはメタクラスtypeのインスタンスである事は既に述べた。

特殊フィールド__metaclass__にメタクラスを指定する事によりデフォルトのメタクラスtypeのかわりに別のメタクラスを指定する事ができる。
__metaclass__には、呼び出し可能(callable)なオブジェクトを指定する。
そして、通常、メタクラスにはクラスtypeを継承したクラスが指定される。

  • __metaclass__ - データモデル — Python 2.7ja1 documentation

    dict['__metaclass__'] があればそれを使います。 それ以外の場合で、最低でも一つ基底クラスを持っているなら、基底クラスのメタクラス (__class__ 属性を探し、なければ基底クラスの型) を使います。 それ以外の場合で、__metaclass__ という名前のグローバル変数があれば、それをつかいます。 それ以外の場合には、旧スタイルのメタクラス (types.ClassType) を使います。

__metaclass__に関数を指定

__metaclass__には呼び出し可能なオブジェクトが指定できる。
という事は、type関数のようにname, bases, dictの3つの引数をとりtypeクラスのインスタンスを返す関数も指定可能。
ここでは、少し回り道をして、__metaclass__に関数を指定する例をみていくことにする。

def metaclass_func(name, bases, dict):
    dict["add_c_var"]=u"メタクラスにより追加されたクラス変数"
    return type(name,bases,dict)

class MyCls(object):
    c_var=u"クラス変数"
    __metaclass__=metaclass_func

print MyCls.c_var,MyCls.add_c_var

上記の例は、クラス変数add_c_varを追加する関数metaclass_funcを特殊フィールド__metaclass__に指定した例である。

また、このコードはクラス内に__metaclass__関数を定義して以下のように記述する事もできる。

class MyCls(object):
    c_var=u"クラス変数"
    def __metaclass__(name, bases, dict):
        dict["add_c_var"]=u"メタクラスにより追加されたクラス変数"
        return type(name,bases,dict)

MyClsの2つのクラス変数の値を確認してみる。

print MyCls.c_var,MyCls.add_c_var

実行結果

クラス変数 メタクラスにより追加されたクラス変数

__metaclass__により追加されたクラス変数MyCls.add_c_varが定義されているのが確認できる。

__metaclass__にtypeクラスを継承したクラスを指定

typeクラスを継承してメタクラスを定義する場合、3つの特殊メソッド__new__,__init__,__call__をフックすることでメタクラスをカスタマイズする事ができそうである。

メタクラスのテンプレートとなるクラスを定義して、動作を確認してみた。


メタクラスを定義。

class MyMeta(type):

    def __call__(self, arg):
        # selfはメタクラスのインスタンスすなわちメタクラスによって新しく生成されるクラスになる。
        print "__call__",arg
        super(MyMeta,self).__call__()

    def __new__(cls, name, bases, dict):
        # clsはメタクラスすなわちクラスメソッド__new__が実装されているクラス(ここではMyMetaクラス)を指す。
        print "__new__"
        return super(MyMeta, cls).__new__(cls, name, bases, dict)

    def __init__(self, name, bases, dict):
        print "__init__"
        super(MyMeta,self).__init__(name, bases, dict)

メタクラスのインスタンスとなるクラスを定義

class MyCls(object):
    __metaclass__=MyMeta

    def __new__(cls, arg):
        # clsはここではMyClsクラスを指す。
        print "MyCls.__new__"
        return super(MyCls, cls).__new__(cls)

    def __init__(self, arg):
        # selfはクラスのインスタンスを指す。
        print "MyCls.__init__",arg

実行結果

MyMeta.__new__
MyMeta.__init__

クラスを定義するとメタクラスの__new__メソッドの実行によりクラス(メタクラスのインスタンス)が生成され,メタクラスの__init__メソッドによりクラスの属性がセットされる。


クラスMyClsのインスタンスを生成

my_inst=MyCls("arg")

実行結果

MyMeta.__call__ arg
MyCls.__new__
MyCls.__init__ arg

クラスのインスタンス生成時にメタクラスの__call__メソッドが呼ばれ、引数argが渡される。 そして、MyClsの__new__,__init__メソッドが実行される。


前項の「__metaclass__に関数を指定した例」を「typeクラスを継承してメタクラス」により実装すると以下のようなコードになる。

class MyMeta(type):
    def __new__(cls, name, bases, dict):
        dict["add_c_var"]=u"追加されたクラス変数"
        return super(MyMeta, cls).__new__(cls, name, bases, dict)

class MyCls(object):
    c_var=u"クラス変数"
    __metaclass__=MyMeta

print MyCls.c_var,MyCls.add_c_var

python3の__metaclass__

ところが、python3では

特殊フィールド__metaclass__は削除されていて、

メタクラスは

class クラス名([ス-パクラスのリスト,]metaclass=メタクラス名):

のようにクラスの宣言時に記述する。

前述のサンプルコードはpython3では

class MyCls(object,metaclass=metaclass_func):

とか

class MyCls(object,metaclass=MyMeta):

と記述する事になる。

特殊メソッド__prepare__

python3では__prepare__という特殊メソッドも追加されていて、

  • 3. データモデル — Python 3.4.3 ドキュメント

    3.3.3.2. クラスの名前空間の準備¶(原文)

    適切なメタクラスが識別されたら、次にクラスの名前空間が準備されます。メタクラスに __prepare__ 属性がある場合、それは、namespace = metaclass.__prepare__(name, bases, **kwds) として呼ばれます (ここで追加のキーワード引数は、もしあればクラス定義から来ます)。

    メタクラスに __prepare__ 属性がない場合、クラスの名前空間は空の dict() インスタンスとして初期化されます。

公式ドキュメントをみても意味がさっぱり?

__prepare__メソッドの戻り値に辞書として働くオブジェクト(dict(を継承した)クラスやcollectionsモジュールに定義されえているクラスのオブジェクト)を返してやる事で、 どうやら、クラスの__new__が呼ばれる前に、__new__に渡されるdict引数の初期値を指定できるらしい。

__prepare__メソッドを使うと前述の例は以下のようになる。

class MyMeta(type):
    @classmethod
    def __prepare__(metacls, name, bases):
        return {"add_c_var": u"追加されたクラス変数"}

class MyCls(object,metaclass=MyMeta):
    c_var = u"クラス変数"

print(MyCls.c_var,MyCls.add_c_var)

python3のメタクラスについては以下も参照。

興味深い__prepare__メソッドの応用例が

クラス・ファクトリー

クラス・ファクトリーという手法を使う事によりメタクラスのようにクラスを動的に生成する事ができる。

  • Pythonでのメタクラス・プログラミング

    ファクトリー関数class_with_method() は、ファクトリーに渡されてきたメソッド/関数を内容とするクラスを動的に生成し、返します。クラス自体は、返される前に関数本体内で操作されます。

クラスファクトリを使って前述の「type関数により動的にクラスを生成」のサンプルコードを書き換えると

# コンストラクタのもととなる関数
def init_func(self):
    self.i_var = u"インスタンス変数"

# インスタンスメソッドのもととなる関数
def i_func(self):
    print u"インスタンスメソッドを実行しました"
    print "self.i_var=", self.i_var

# クラスメソッドのもととなる関数
def c_func(cls):
    print u"クラスメソッドを実行しました"
    print "cls.c_var=", cls.c_var

# スタティックメソッドのもととなる関数
def s_func():
    print u"スタティックメソッドを実行しました"

# クラスファクトリ関数
def class_factory_func(dic):
    class klass:
        pass
    for attr_name,attr_value in dic.iteritems():
        setattr(klass, attr_name, attr_value)
    return klass

# クラスオブジェクトの生成
MyCls = class_factory_func(
            dict(
                    c_var=u"クラス変数",
                    __init__=init_func,
                    i_method=i_func,
                    c_method=classmethod(c_func),
                    s_method=staticmethod(s_func),
                 ))

クラス・ファクトリーを使う事でメタクラスのようにクラスをカスタマイズできるのがわかる。

メタクラスの実装および応用例

メタクラスの実装および応用例についてぐぐってみた。


JavaScriptのPrototypeのような継承が可能なPythonのクラス

AOP

Singleton

応用例では無いが、メタクラスをめぐるpythonの歴史も興味深い。

ページのトップへ戻る