2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Python3のメタクラスメモ

Last updated at Posted at 2019-11-17

Python3のメタクラスメモ

Python初心者がPython3のメタクラスについて学んだことのメモ
コード内のコメントはコンソールの出力結果を意図しています。
誤りや不適正な表現があればご指摘いただけると幸いです。

メタクラスとは

あるインスタンスがどのクラス型かを調べる場合、type()を利用することができる。

class Hoge:
    pass
h = Hoge()
print(type(h))
# <class '__main__.Hoge'>

Hogeクラスにもtype()を適用してみる。

print(type(Hoge))
# <class 'type'>

これはHogeクラス自体がtype型のインスタンスであることを示している。hがHogeクラスのインスタンスであると同様に、Hogeクラスもまたtypeクラスのインスタンスということになる。***Python3で定義されたクラスは、typeという特殊なクラス(メタクラス)のインスタンスになる。***通常では、クラスを定義すると3つの引数を持つtype(classname, superclasses, attributes_dict)が呼ばれ、当該クラスのインスタンスが生成される。"通常"の定義方法ではなく、明示的にtypeクラスに3つの引数を与えクラスインスタンスを生成することも可能。上記のHogeクラスはtypeを用いて↓のように定義できる。(第一引数にはクラス名、第二引数には親クラスのタプル、第三引数には名前空間の辞書を渡す)

Hoge = type('Hoge', (), {})
h = Hoge()
print(type(h))
# <class '__main__.Hoge'>

クラスインスタンスを生成するtypeクラスは、クラス(のインスタンス)を作る工場のようなものでメタクラスと呼ばれる。インタプリタがHogeクラスの宣言に直面した際、デフォルトではtypeクラスの__new__が呼ばれクラスインスタンスが生成されるが、このtypeクラスを継承することでメタクラスをカスタマイズすることができる。下記は、クラスの宣言時にデフォルトのtypeではなくtypeを継承したMetaTestクラスをメタクラスに設定した例である。

class MetaTest(type):
    def __new__(cls, clsname, superclasses, attributedict):
        print("メタクラスのテスト")
class HogeHoge(metaclass=MetaTest):
    pass
# メタクラスのテスト
h = HogeHoge()
# TypeError: 'NoneType' object is not callable

MetaTestはデフォルトのメタクラスであるtypeクラスを継承し、クラスインスタンスを生成する__new__メソッドをオーバーライドしている。ここではクラスインスタンスを生成することなく、ただ文字列を出力している。そのため、MetaTestをメタクラスに設定したHogeHogeクラスを宣言した際、クラスインスタンスを生成するtype.__new__ではなく、文字列を返すだけのMetaTest.__new__が呼ばれる。コンソールにも「メタクラスのテスト」と表示された。次にHogeHogeクラスのインスタンスを生成しようとしたところ、エラーとなった。MetaTestクラスではHogeHogeクラスのインスタンスが生成されていない(Noneオブジェクトが生成されている?)。親クラスであるtypeの__new__をオーバーライドしてしまうと、クラスインスタンスが生成されなくなってしまう。__new__の戻り値にtype.__new__を呼んでクラスインスタンスを返すか、__init__をオーバーライドするとうまくいく(__new__が先に呼ばれた段階でクラスインスタンス生成済み?)。

class MetaTestA(type):
    def __new__(cls, clsname, superclasses, attributedict):
        print("メタクラスAのテスト")
        return type.__new__(cls, clsname, superclasses, attributedict)
class MetaTestB(type):
    def __init__(cls, clsname, superclasses, attributedict):
        print("メタクラスBのテスト")
class HogeHogeA(metaclass=MetaTestA):
    pass
# メタクラスAのテスト
class HogeHogeB(metaclass=MetaTestB):
    pass
# メタクラスBのテスト
a = HogeHogeA()
b = HogeHogeB()
print(type(a))
# <class '__main__.HogeHogeA'>
print(type(b))
#  <class '__main__.HogeHogeB'>

メタクラスの活用例(Singleton)

メタクラスをカスタマイズすることでクラスインスタンスの振る舞いを制御できる。今回参考にしたこちらのサイトでは、メタクラスを活用した↓のようなSingletonパターンが紹介されている。

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class SingletonClass(metaclass=Singleton):
    pass
class RegularClass():
    pass
x = SingletonClass()
y = SingletonClass()
print(x == y)
# True
x = RegularClass()
y = RegularClass()
print(x == y)
# False

__call__メソッドを持つインスタンス(callableというらしい)は、インスタンス名()で関数のように__call__を呼び出すことができる。typeを継承したメタクラスであるSingletonはtypeクラスの__call__をオーバーライドしている。Singletonをメタクラスとして設定したSingletonClassクラス(Singletonのインスタンスであるクラス)が、SingletonClass()の形式で記述されるたびに(SingletonClassをインスタンス化するたびに)、__call__が呼びだされる。__call__内部では、_instancesにインスタンスが格納されているかをチェックし、インスタンスが存在しない場合はSingletonの親クラスであるtypeの__call__メソッドが呼び出され、SingletonClassがインスタンス化される(ここでtypeの__call__がSingletonClassをインスタンス化しているようだけど詳しいことはよく分からない。)。すでに_instancesにインスタンスが存在する場合は、新たにインスタンスを生成することはない。一方メタクラスがデフォルトのtypeクラスであるRegularClassは、都度インスタンスが生成されている。

##参考
https://www.yunabe.jp/docs/python_metaclass.html
https://www.python-course.eu/python3_metaclasses.php
https://realpython.com/python-metaclasses/
https://teratail.com/questions/180387

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?