32

私は Python メタクラスを理解しようとしてきたので、いくつかのサンプル コードを調べてきました。私が理解している限り、Python メタクラスは任意の呼び出し可能にすることができます。したがって、メタクラスを次のようにすることができます

def metacls(clsName, bases, atts):
    ....
    return type(clsName, bases, atts)

しかし、私は多くの人がメタクラスを次のように書いているのを見てきました:

class Metacls(type):
    def __new__(meta, clsName, bases, atts):
        ....
        return type.__new__(meta, clsName, bases, atts)

私が見る限り、これらは両方とも同じことをします。代わりに基本クラスを使用する理由はありますか? 慣習ですか?

4

1 に答える 1

43

主に継承に関連する微妙な違いがあります。関数をメタクラスとして使用する場合、結果のクラスは実際には のインスタンスであり、無制限にtype継承できます。ただし、そのようなサブクラスに対してメタクラス関数が呼び出されることはありません。のサブクラスをtypeメタクラスとして使用すると、結果のクラスは、そのサブクラスと同様に、そのメタクラスのインスタンスになります。ただし、多重継承は制限されます。

違いを示す:

>>> def m1(name, bases, atts):
>>>     print "m1 called for " + name
>>>     return type(name, bases, atts)
>>>

>>> def m2(name, bases, atts):
>>>     print "m2 called for " + name
>>>     return type(name, bases, atts)
>>>

>>> class c1(object):
>>>     __metaclass__ = m1
m1 called for c1

>>> type(c1)
<type 'type'>

>>> class sub1(c1):
>>>     pass

>>> type(sub1)
<type 'type'>

>>> class c2(object):
>>>     __metaclass__ = m2
m2 called for c2

>>> class sub2(c1, c2):
>>>     pass

>>> type(sub2)
<type 'type'>

sub1 と sub2 を定義するときに、メタクラス関数が呼び出されていないことに注意してください。それらは、c1 と c2 にメタクラスがなく、作成後に操作されたかのように作成されます。

>>> class M1(type):
>>>     def __new__(meta, name, bases, atts):
>>>         print "M1 called for " + name
>>>         return super(M1, meta).__new__(meta, name, bases, atts)

>>> class C1(object):
>>>     __metaclass__ = M1
M1 called for C1

>>> type(C1)
<class '__main__.M1'>

>>> class Sub1(C1):
>>>     pass
M1 called for Sub1

>>> type(Sub1)
<class '__main__.M1'>

すでに違いがあることに注意してください。M1 は Sub1 の作成時に呼び出され、両方のクラスが M1 のインスタンスです。super()後で明らかになる理由で、ここで実際の作成に使用しています。

>>> class M2(type):
>>>     def __new__(meta, name, bases, atts):
>>>         print "M2 called for " + name
>>>         return super(M2, meta).__new__(meta, name, bases, atts)

>>> class C2(object):
>>>     __metaclass__ = M2
M2 called for C2

>>> type(C2)
<class '__main__.M2'>

>>> class Sub2(C1, C2):
>>>     pass
M1 called for Sub2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 23, in __new__
TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

これは、メタクラスによる多重継承の主な制限です。Python は、M1 と M2 が互換性のあるメタクラスであるかどうかを認識していないため、必要なことを保証するために、新しいメタクラスを作成する必要があります。

>>> class M3(M1, M2):
>>>     def __new__(meta, name, bases, atts):
>>>         print "M3 called for " + name
>>>         return super(M3, meta).__new__(meta, name, bases, atts)

>>> class C3(C1, C2):
>>>     __metaclass__ = M3
M3 called for C3
M1 called for C3
M2 called for C3

>>> type(C3)
<class '__main__.M3'>

super()これが、メタクラス関数で使用した理由です__new__。つまり、それぞれが MRO で次の関数を呼び出すことができます。

特定のユースケースでは、クラスを typetypeにする必要がある場合や、継承の問題を回避したい場合があります。その場合は、メタクラス関数がおそらく最適です。また、クラスの型が本当に重要な場合や、すべてのサブクラスを操作したい場合もあります。この場合は、サブクラス化 typeを行う方が適切です。あらゆる状況に最適なスタイルを自由に使用してください。

于 2010-01-27T21:21:58.797 に答える