(2016/10/04追記) 以下はPython3.5で確認。Python2.xではsuperを引数なしで呼べないためそのままでは実行できず、かつ実行結果も同等のものになるかは不明。
__new__
と__init__
Pythonでは、__new__
メソッド、__init__
メソッドの2つのメソッドを実装したクラスを生成することで、そのクラスのインスタンス作成時に行う処理を定義することができる。
class A(object):
def __new__(cls):
print('new')
return super().__new__(cls)
def __init__(self):
print('init')
super().__init__()
>>> A()
new
init
<__main__.A object at 0x10da40050>
上記の例を見るに、__new__
と__init__
はどちらもインスタンス生成時に呼ばれるため、初期化処理などはどちらに書いてもよいように思える。
では、この2つのメソッドの違いは何か?
実はインスタンス生成時に「必ず」呼ばれる__new__
と違い、__init__
はクラスの実装次第では呼ばれないことがある。
呼ばれない例を以下に示す。
# 1. __new__でインスタンスをreturnしない場合
# ref. https://docs.python.org/2/reference/datamodel.html#object.__new__
class A(object):
def __new__(cls):
print('new')
# return super().__new__(cls)
def __init__(self):
print('init')
super().__init__()
>>> A()
new # __new__は呼ばれ、__init__は呼ばれない
# 2. サブクラスで親クラスの__init__を明示的に呼び出さない場合
class B(A):
pass
...
>>> B()
new # クラスAの__new__は呼ばれ、__init__は呼ばれない
特に2のケースは、クラスが継承して使用することを想定している場合に発生しうる。その場合は、__init__
ではなく、__new__
で必要な初期化処理を行うべき。
追記(2014/04/13)
コメントで指摘されたとおり、1のケースは実用的には起こりえない。
__new__
は引数として与えたクラスのインスタンスをreturnするべきであり、そうしていないインスタンスは使いものにならないからだ。
(ref) https://docs.python.org/2/reference/datamodel.html#object.__new__
メタクラス
インスタンス生成時に行う処理を追加できる__init__
, __new__
に対し、メタクラスを使うことでクラス定義時(=クラスを表すオブジェクトの生成時?)に行う処理を追加できる。
メタクラスを定義するには、typeクラスを継承したクラスを生成するのが一番簡単である。
継承したクラスで、クラスメソッド__new__
(クラス名、親クラス、クラス属性を定義する辞書)を実装する。
(なお、この__new__
は、インスタンス生成で呼ばれる__new__
とは異なることに注意。
メタクラスの__new__
はtypeクラスのメソッドを、インスタンス生成時の__new__
はobjectクラスのメソッドを、それぞれオーバーライドしている)
class MetaClass(type):
def __new__(cls, cls_name, cls_bases, cls_dict):
print('meta')
return super().__new__(cls, cls_name, cls_bases, cls_dict)
メタクラスは以下のように指定する。Python2とPython3で指定の仕方が異なることに注意。
# メタクラスのクラス名はMetaClassとする。
# python2
class C(object):
__metaclass__ = MetaClass
...
# python3
class D(object, metaclass=MetaClass):
pass
# インタープリタで逐次実行すると
# クラス定義が完了した時点でメタクラスで定義した処理が実行されることが分かる。
>>> class D(object, metaclass=MetaClass):
... pass
...
meta
以下の例では、クラス名(の表示)を変更するメタクラスを定義している。
class ChangeNameMeta(type):
def __new__(cls, cls_name, cls_bases, cls_dict):
return super().__new__(cls, 'change_name', cls_bases, cls_dict)
class E(object, metaclass=ChangeNameMeta):
pass
# インタープリタでの実行結果
>>> print(E().__str__)
<method-wrapper '__str__' of change_name object at 0x10da45fd0>
上記の例は実用的ではないが、上手く使うといろいろ面白いことができそう。
(ref)
http://coreblog.org/ats/lerning-metaclass-by-reading-javascript-like-prototype-in-python/
http://momijiame.tumblr.com/post/50722263621/python