メタクラスについて議論するとき、 ドキュメント 状態:
もちろん、他のクラスメソッドをオーバーライドする(または新しいメソッドを追加する)こともできます。たとえば、メタクラスでカスタム
__call__()
メソッドを定義すると、クラスが呼び出されたときのカスタム動作が可能になります。常に新しいインスタンスを作成するわけではありません。
私の質問は次のとおりです。たとえば、クラスが呼び出されたときに、新しいオブジェクトを作成するのではなく、キャッシュするなどのカスタム動作が必要だとします。これを行うには、クラスの__new__
メソッドをオーバーライドします。代わりに__call__
を使用してメタクラスを定義する必要があるのはいつですか?このアプローチでは、__new__
では実現できないことは何ですか?
あなたの質問への直接の答えは、インスタンスの作成をカスタマイズするだけではなくmoreしたいとき、またはクラスdoes作成方法から。
Pythonでのシングルトンの作成 への私の回答と関連する議論を参照してください。
いくつかの利点があります。
クラスが行うことと、作成方法の詳細を分離することができます。メタクラスとクラスはそれぞれ1つのことに責任があります。
メタクラスにコードを1回記述し、それを使用して、複数の継承を気にすることなく、いくつかのクラスの呼び出し動作をカスタマイズできます。
サブクラスは__new__
メソッドの動作をオーバーライドできますが、メタクラスの__call__
は__new__
を呼び出す必要すらありません。
セットアップ作業がある場合は、メタクラスの__new__
メソッドで行うことができます。これは、クラスが呼び出されるたびではなく、1回だけ行われます。
単一責任の原則について心配していなければ、__new__
のカスタマイズがうまく機能するケースは確かにたくさんあります。
しかし、インスタンスが作成されるときではなく、クラスが作成されるときに、より早く行わなければならない他のユースケースがあります。メタクラスが必要になるのは、これらがプレイされるときです。多くの優れた例については、 Pythonでのメタクラスの(具体的な)ユースケースは何ですか? を参照してください。
これらのメソッドの実行順序を注意深く観察すると、微妙な違いが少しわかりやすくなります。
_class Meta_1(type):
def __call__(cls, *a, **kw):
print "entering Meta_1.__call__()"
rv = super(Meta_1, cls).__call__(*a, **kw)
print "exiting Meta_1.__call__()"
return rv
class Class_1(object):
__metaclass__ = Meta_1
def __new__(cls, *a, **kw):
print "entering Class_1.__new__()"
rv = super(Class_1, cls).__new__(cls, *a, **kw)
print "exiting Class_1.__new__()"
return rv
def __init__(self, *a, **kw):
print "executing Class_1.__init__()"
super(Class_1,self).__init__(*a, **kw)
_
上記のコードは実際にはdoを実行していないことに注意してください。各メソッドは、その親の実装、つまりデフォルトに従います。したがって、ログを記録するだけでなく、次のように宣言したかのようになります。
_class Meta_1(type): pass
class Class_1(object):
__metaclass__ = Meta_1
_
次に、_Class_1
_のインスタンスを作成しましょう
_c = Class_1()
# entering Meta_1.__call__()
# entering Class_1.__new__()
# exiting Class_1.__new__()
# executing Class_1.__init__()
# exiting Meta_1.__call__()
_
したがって、type
が_Meta_1
_の親である場合、type.__call__()
の疑似実装を次のように想像できます。
_class type:
def __call__(cls, *args, **kwarg):
# ... a few things could possibly be done to cls here... maybe... or maybe not...
# then we call cls.__new__() to get a new object
obj = cls.__new__(cls, *args, **kwargs)
# ... a few things done to obj here... maybe... or not...
# then we call obj.__init__()
obj.__init__(*args, **kwargs)
# ... maybe a few more things done to obj here
# then we return obj
return obj
_
上記の呼び出し順序から、Meta_1.__call__()
(またはこの場合はtype.__call__()
)にClass_1.__new__()
およびClass_1.__init__()
は最終的に作成されます。その実行の過程で、Meta_1.__call__()
は、どちらにも触れられていないオブジェクトを返す可能性があります。シングルトンパターンへのこのアプローチを例にとります:
_class Meta_2(type):
__Class_2_singleton__ = None
def __call__(cls, *a, **kw):
# if the singleton isn't present, create and register it
if not Meta_2.__Class_2_singleton__:
print "entering Meta_2.__call__()"
Meta_2.__Class_2_singleton__ = super(Meta_2, cls).__call__(*a, **kw)
print "exiting Meta_2.__call__()"
else:
print ("Class_2 singleton returning from Meta_2.__call__(), "
"super(Meta_2, cls).__call__() skipped")
# return singleton instance
return Meta_2.__Class_2_singleton__
class Class_2(object):
__metaclass__ = Meta_2
def __new__(cls, *a, **kw):
print "entering Class_2.__new__()"
rv = super(Class_2, cls).__new__(cls, *a, **kw)
print "exiting Class_2.__new__()"
return rv
def __init__(self, *a, **kw):
print "executing Class_2.__init__()"
super(Class_2, self).__init__(*a, **kw)
_
タイプ_Class_2
_のオブジェクトを繰り返し作成しようとするとどうなるかを見てみましょう
_a = Class_2()
# entering Meta_2.__call__()
# entering Class_2.__new__()
# exiting Class_2.__new__()
# executing Class_2.__init__()
# exiting Meta_2.__call__()
b = Class_2()
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped
c = Class_2()
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped
print a is b is c
True
_
次に、クラスの__new__()
メソッドを使用してこの実装を観察し、同じことを実行してみます。
_import random
class Class_3(object):
__Class_3_singleton__ = None
def __new__(cls, *a, **kw):
# if singleton not present create and save it
if not Class_3.__Class_3_singleton__:
print "entering Class_3.__new__()"
Class_3.__Class_3_singleton__ = rv = super(Class_3, cls).__new__(cls, *a, **kw)
rv.random1 = random.random()
rv.random2 = random.random()
print "exiting Class_3.__new__()"
else:
print ("Class_3 singleton returning from Class_3.__new__(), "
"super(Class_3, cls).__new__() skipped")
return Class_3.__Class_3_singleton__
def __init__(self, *a, **kw):
print "executing Class_3.__init__()"
print "random1 is still {random1}".format(random1=self.random1)
# unfortunately if self.__init__() has some property altering actions
# they will affect our singleton each time we try to create an instance
self.random2 = random.random()
print "random2 is now {random2}".format(random2=self.random2)
super(Class_3, self).__init__(*a, **kw)
_
上記の実装は、クラスにシングルトンを正常に登録しても、__init__()
の呼び出しを妨げないことに注意してください。これは、暗黙的にtype.__call__()
で発生します(type
がデフォルトです)何も指定されていない場合はメタクラス)。これにより、いくつかの望ましくない影響が生じる可能性があります。
_a = Class_3()
# entering Class_3.__new__()
# exiting Class_3.__new__()
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.739298365475
b = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.247361634396
c = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.436144427555
d = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.167298405242
print a is b is c is d
# True
_
1つの違いは、メタクラス__call__
メソッドを定義することにより、クラスまたはサブクラスの__new__
メソッドのいずれかが呼び出される機会を得る前に、それが呼び出されることを要求することです。
class MetaFoo(type):
def __call__(cls,*args,**kwargs):
print('MetaFoo: {c},{a},{k}'.format(c=cls,a=args,k=kwargs))
class Foo(object):
__metaclass__=MetaFoo
class SubFoo(Foo):
def __new__(self,*args,**kwargs):
# This never gets called
print('Foo.__new__: {a},{k}'.format(a=args,k=kwargs))
sub=SubFoo()
foo=Foo()
# MetaFoo: <class '__main__.SubFoo'>, (),{}
# MetaFoo: <class '__main__.Foo'>, (),{}
SubFoo.__new__
が呼び出されないことに注意してください。対照的に、メタクラスなしでFoo.__new__
を定義すると、サブクラスがFoo.__new__
をオーバーライドできるようになります。
もちろん、MetaFoo.__call__
を定義してcls.__new__
を呼び出すこともできますが、それはあなた次第です。これを拒否することで、サブクラスが__new__
メソッドを呼び出されないようにすることができます。
ここでメタクラスを使用することには説得力のある利点はありません。また、「シンプルは複雑よりも優れている」ので、__new__
の使用をお勧めします。
肉付けされたPython 3バージョンのパイロスコープの答えは、誰かがコピー、貼り付け、ハッキングするのに便利かもしれないと思った この記事 から取られます:
class Meta(type):
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
print(' Meta.__prepare__(mcs=%s, name=%r, bases=%s, **%s)' % (
mcs, name, bases, kwargs
))
return {}
def __new__(mcs, name, bases, attrs, **kwargs):
print(' Meta.__new__(mcs=%s, name=%r, bases=%s, attrs=[%s], **%s)' % (
mcs, name, bases, ', '.join(attrs), kwargs
))
return super().__new__(mcs, name, bases, attrs)
def __init__(cls, name, bases, attrs, **kwargs):
print(' Meta.__init__(cls=%s, name=%r, bases=%s, attrs=[%s], **%s)' % (
cls, name, bases, ', '.join(attrs), kwargs
))
super().__init__(name, bases, attrs)
def __call__(cls, *args, **kwargs):
print(' Meta.__call__(cls=%s, args=%s, kwargs=%s)' % (
cls, args, kwargs
))
return super().__call__(*args, **kwargs)
print('** Meta class declared')
class Class(metaclass=Meta, extra=1):
def __new__(cls, myarg):
print(' Class.__new__(cls=%s, myarg=%s)' % (
cls, myarg
))
return super().__new__(cls)
def __init__(self, myarg):
print(' Class.__init__(self=%s, myarg=%s)' % (
self, myarg
))
self.myarg = myarg
super().__init__()
def __str__(self):
return "<instance of Class; myargs=%s>" % (
getattr(self, 'myarg', 'MISSING'),
)
print('** Class declared')
Class(1)
print('** Class instantiated')
出力:
** Meta class declared
Meta.__prepare__(mcs=<class '__main__.Meta'>, name='Class', bases=(), **{'extra': 1})
Meta.__new__(mcs=<class '__main__.Meta'>, name='Class', bases=(), attrs=[__module__, __qualname__, __new__, __init__, __str__, __classcell__], **{'extra': 1})
Meta.__init__(cls=<class '__main__.Class'>, name='Class', bases=(), attrs=[__module__, __qualname__, __new__, __init__, __str__, __classcell__], **{'extra': 1})
** Class declared
Meta.__call__(cls=<class '__main__.Class'>, args=(1,), kwargs={})
Class.__new__(cls=<class '__main__.Class'>, myarg=1)
Class.__init__(self=<instance of Class; myargs=MISSING>, myarg=1)
** Class instantiated
同じ記事で強調されているもう1つの優れたリソースは、David BeazleyのPyCon 2013 Python 3メタプログラミングチュートリアル です。
これは、ライフサイクルのフェーズとアクセスできる内容の問題です。 __call__
が呼び出されますafter__new__
および初期化パラメーターが渡されますbefore__init__
に渡されるため、それらを操作できます。このコードを試して、その出力を調べてください。
class Meta(type):
def __new__(cls, name, bases, newattrs):
print "new: %r %r %r %r" % (cls, name, bases, newattrs,)
return super(Meta, cls).__new__(cls, name, bases, newattrs)
def __call__(self, *args, **kw):
print "call: %r %r %r" % (self, args, kw)
return super(Meta, self).__call__(*args, **kw)
class Foo:
__metaclass__ = Meta
def __init__(self, *args, **kw):
print "init: %r %r %r" % (self, args, kw)
f = Foo('bar')
print "main: %r" % f