LoginSignup
106
95

More than 5 years have passed since last update.

__new__と__init__とメタクラスと

Last updated at Posted at 2014-04-09

(2016/10/04追記) 以下はPython3.5で確認。Python2.xではsuperを引数なしで呼べないためそのままでは実行できず、かつ実行結果も同等のものになるかは不明。

__new____init__

Pythonでは、__new__メソッド、__init__メソッドの2つのメソッドを実装したクラスを生成することで、そのクラスのインスタンス作成時に行う処理を定義することができる。

__new__と__init__
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__はクラスの実装次第では呼ばれないことがある。
呼ばれない例を以下に示す。

__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

106
95
2

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
106
95