web-dev-qa-db-ja.com

メタクラスの継承

このよく知られている answer では、Pythonのメタクラスについて説明しています。 ___metaclass___属性は継承されないと記載されています。

しかし、実際のところ、私はPythonで試しました。

_class Meta1(type):
    def __new__(cls, clsname, bases, dct):
        print "Using Meta1"
        return type.__new__(cls, clsname, bases, dct)

# "Using Meta1" printed
class Foo1:
    __metaclass__ = Meta1

# "Using Meta1" printed
class Bar1(Foo1):
    pass
_

予想どおり、FooBarはどちらもメタクラスとして_Meta1_を使用し、予想どおりに文字列を出力します。

ただし、次のサンプルでは、​​type(...)の代わりにtype.__new__(...)が返されると、メタクラスは継承されなくなります。

_class Meta2(type):
    def __new__(cls, clsname, bases, dct):
        print "Using Meta2"
        return type(clsname, bases, dct)

# "Using Meta2" printed
class Foo2:
    __metaclass__ = Meta2

# Nothing printed
class Bar2(Foo2):
    pass
_

___metaclass___属性と___class___属性を調べると、次のことがわかります。

_print Foo1.__metaclass__ # <class '__main__.Meta1'>
print Bar1.__metaclass__ # <class '__main__.Meta1'>
print Foo2.__metaclass__ # <class '__main__.Meta2'>
print Bar2.__metaclass__ # <class '__main__.Meta2'>

print Foo1.__class__ # <class '__main__.Meta1'>
print Bar1.__class__ # <class '__main__.Meta1'>
print Foo2.__class__ # <type 'type'>
print Bar2.__class__ # <type 'type'>
_

結論として:

  1. ___metaclass___と___class___の両方が基本クラスから継承されます

  2. _Meta2_は実際にはtypeですが、_Foo2_で定義された作成動作は_Foo2.__class___に使用されます。

  3. ___metaclass___の_Bar2_属性は_Meta2_ですが、_Bar2_の作成動作は影響を受けません。別の言葉で言えば、_Bar2_は_Meta2_の代わりにtypeを「実際の」メタクラスとして使用します。

これらの観察は、___metaclass___の継承メカニズムを私には曖昧にします。

私の推測はそれです:

  1. クラス(例:_Meta1_)を別のクラス 'Foo1'の___metaclass___属性に直接割り当てる場合、それは___metaclass___属性が有効になります。

  2. サブクラスが定義時に___metaclass___属性を明示的に設定しない場合。基本クラスの___class___属性の代わりに___metaclass___属性が、サブクラスの「実際の」メタクラスを決定します。

私の推測は正しいですか? Pythonはメタクラスの継承をどのように処理しますか?

12
Lifu Huang

あなたは多くのことを推測していますが、Pythonのミニマリストと「特別な場合はルールを破るほど特別ではありません」。ディレクティブ、それよりも理解しやすくします。

Python2では、クラス本体の___metaclass___属性は、クラスの作成時に、そのクラスが存在する「クラス」を呼び出すために使用されます。通常、それはtypeという名前のクラスです。明確にするために、その瞬間は、パーサーがクラス本体を解析した後、コンパイラーがクラス本体をコンパイルした後、プログラムの実行時に実際に実行された後、___metaclass___が明示的に指定されている場合に限ります。クラス本体。

それでは、次のような場合に方法を確認しましょう。

_class A(object):
    __metaclass__ = MetaA

class B(A):
    pass
_

Aの本体には___metaclass___があります-MetaAの代わりにtypeが呼び出され、「クラスオブジェクト」になります。 Bの本体には___metaclass___がありません。作成後、___metaclass___属性にアクセスしようとすると、他の属性と同じように表示されます。これは、PythonがスーパークラスAから取得するためです。 _A.__dict___をチェックすると、___metaclass___が表示されますが、_B.__dict___をチェックしても表示されません。

この_A.__metaclass___属性は、Bが作成されたときに未使用です。 Aを宣言する前にBで変更した場合でも、Aと同じメタクラスが使用されます-Pythonは、明示的な___metaclass___。

説明する:

_In [1]: class M(type): pass

In [2]: class A(object): __metaclass__ = M

In [3]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(A.__class__, A.__metaclass__, A.__dict__.get("__metaclass__"), type(A))
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: <class '__main__.M'>, type: <class '__main__.M'>

In [4]: class B(A): pass

In [5]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(B.__class__, B.__metaclass__, B.__dict__.get("__metaclass__"), type(B))
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: None, type: <class '__main__.M'>

In [6]: A.__metaclass__ = type

In [8]: class C(A): pass

In [9]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(C.__class__, C.__metaclass__, C.__dict__.get("__metaclass__"), type(C))
class: <class '__main__.M'>, metaclass_attr: <type 'type'>, metaclass_in_dict: None, type: <class '__main__.M'>
_

さらに、typeステートメントで本体を使用する代わりに、classの呼び出しを介してクラスを作成しようとすると、___metaclass___も通常の属性になります。

_In [11]: D = type("D", (object,), {"__metaclass__": M})

In [12]: type(D)
type
_

これまでの要約:Python 2の___metaclass___属性は、実行の一部としてクラス本体宣言に明示的に配置されている場合にのみ特別ですclassブロックステートメントの。これは通常の属性であり、後で特別なプロパティはありません。

Python3は両方とも、この奇妙な「___metaclass___属性は今は良くない」を取り除き、メタクラスを指定するように構文を変更することでクラス本体をさらにカスタマイズできるようにしました。 (これは、metaclassステートメント自体で「classnamedparameter」であるかのように宣言されているようなものです)

さて、あなたの疑問を提起したものの2番目の部分に:メタクラスの___new___メソッドで_type.__new___の代わりにtypeを呼び出す場合、方法はありませんPython "know" typeは派生メタクラスから呼び出されています。_type.__new___を呼び出すときは、最初のパラメーターとしてcls属性を渡します。メタクラスの___new___自体がランタイムによって渡されます。クラスはtypeのサブクラスのインスタンスです。これは、Pythonの他のクラスで継承が機能するのと同じです-したがって、ここでは「特別な動作はありません」:

だから、違いを見つける:

_class M1(type):
    def __new__(metacls, name, bases, attrs):
         cls = type.__new__(metacls, name, bases, attrs)
         # cls now is an instance of "M1"
         ...
         return cls


class M2(type):
    def __new__(metacls, name, bases, attrs):
         cls = type(cls, name, bases, attrs)
         # Type does not "know" it was called from within "M2"
         # cls is an ordinary instance of "type"
         ...
         return cls
_

インタラクティブなプロンプトで確認できます。

_In [13]: class M2(type):
   ....:     def __new__(metacls, name, bases, attrs):
   ....:         return type(name, bases, attrs)
   ....:     

In [14]: class A(M2): pass

In [15]: type(A)
Out[15]: type

In [16]: class A(M2): __metaclass__ = M2

In [17]: A.__class__, A.__metaclass__
Out[17]: (type, __main__.M2)
_

(メタクラス___new___メソッドの最初のパラメーターはメタクラス自体であるため、コードや「実際の」多くのコードでは、metaclsよりもclsという名前が適切であることに注意してください)

6
jsbueno