15

今日、私はここでPythonのメタクラスの驚くべき定義に出くわしました。メタクラスの定義は、効果的にインライン化されています。関連する部分は

class Plugin(object):
    class __metaclass__(type):
        def __init__(cls, name, bases, dict):
            type.__init__(name, bases, dict)
            registry.append((name, cls))

このようなインライン定義を使用するのはいつ意味がありますか?

さらなる議論:

1つの方法として、作成されたメタクラスはこの手法を使用して他の場所で再利用できないという議論があります。反対の議論は、メタクラスを使用する際の一般的なパターンは、メタクラスを定義し、それを1つのクラスで使用し、それから継承することです。たとえば、保守的なメタクラスでは、定義

class DeclarativeMeta(type):
    def __new__(meta, class_name, bases, new_attrs):
        cls = type.__new__(meta, class_name, bases, new_attrs)
        cls.__classinit__.im_func(cls, new_attrs)
        return cls
class Declarative(object):
    __metaclass__ = DeclarativeMeta
    def __classinit__(cls, new_attrs): pass

次のように書くことができます

class Declarative(object):  #code not tested!
    class __metaclass__(type):
        def __new__(meta, class_name, bases, new_attrs):
            cls = type.__new__(meta, class_name, bases, new_attrs)
            cls.__classinit__.im_func(cls, new_attrs)
            return cls
    def __classinit__(cls, new_attrs): pass

その他の考慮事項はありますか?

4

1 に答える 1

20

ネストされたクラス定義の他のすべての形式と同様に、ネストされたメタクラスは、多くの種類の「本番環境での使用」に対して、より「コンパクトで便利」な場合があります(継承以外でそのメタクラスを再利用しなくても問題ない場合)。デバッグとイントロスペクションには不便です。

__module__基本的に、メタクラスに適切なトップレベルの名前を付ける代わりに、モジュールで定義されたすべてのカスタムメタクラスが、それらの属性と__name__属性(Pythonがreprifを形成するために使用するもの)に基づいて互いに区別できないようになります。必要)。検討:

>>> class Mcl(type): pass
... 
>>> class A: __metaclass__ = Mcl
...
>>> class B:
...   class __metaclass__(type): pass
... 
>>> type(A)
<class '__main__.Mcl'>
>>> type(B)
<class '__main__.__metaclass__'>

IOW、「どのタイプがクラスAであるか」(メタクラスはクラスのタイプです、覚えておいてください)を調べたい場合は、明確で有用な答えが得られMclます。これはメインモジュールにあります。ただし、「どのタイプがクラスBであるか」を調べたい場合、答えはそれほど有用ではありません。モジュール__metaclass__にあると表示されますが、それは真実ではありません。main

>>> import __main__
>>> __main__.__metaclass__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute '__metaclass__'
>>> 

...実際にはそのようなことはありません。そのreprは誤解を招きやすく、あまり役に立ちません;-)。

クラスのreprは本質的に'%s.%s' % (c.__module__, c.__name__)-シンプルで便利で一貫性のあるルール-ですが、多くの場合、classステートメントがモジュールスコープで一意ではない、またはモジュールスコープにまったくない(関数またはクラス本体内ではない)などです。 )、または存在しない場合(もちろんclass、メタクラスを明示的に呼び出すことにより、クラスをステートメントなしで構築できます)、これは多少誤解を招く可能性があります(そして、最善の解決策は、これらの特殊なケースを可能な限り回避することです。それらを使用することにより、実質的な利点を得ることができます)。たとえば、次のことを考慮してください。

>>> class A(object):
...   def foo(self): print('first')
... 
>>> x = A()
>>> class A(object):
...   def foo(self): print('second')
... 
>>> y = A()
>>> x.foo()
first
>>> y.foo()
second
>>> x.__class__
<class '__main__.A'>
>>> y.__class__
<class '__main__.A'>
>>> x.__class__ is y.__class__
False

同じスコープに2つのclassステートメントがある場合、2番目のステートメントは名前(ここではA)を再バインドしますが、既存のインスタンスは名前ではなくオブジェクトによる名前の最初のバインドを参照します。したがって、両方のクラスオブジェクトが残り、一方はtype(または__class__そのインスタンスの属性)(存在する場合-存在しない場合、その最初のオブジェクトは消えます)-2つのクラスは同じ名前とモジュール(したがって同じ表現)を持ちますが、それらは別個のオブジェクトです。クラスまたは関数本体内にネストされたクラス、またはメタクラス(を含むtype)を直接呼び出すことによって作成されたクラスは、デバッグまたはイントロスペクションが必要になった場合に同様の混乱を引き起こす可能性があります。

したがって、メタクラスをネストすることは、そのコードをデバッグしたり、他の方法でイントロスペクトしたりする必要がない場合は問題ありません。また、メタクラスを実行している人がこの癖を理解していれば、一緒に暮らすことができます(ただし、素敵な本名を使用するほど便利ではありませんが、もちろん、でコーディングされた関数をデバッグするのと同じように、でコーディングされた関数をデバッグするlambdaほど便利なことはありませんdeflambdavsとの類推によりdef、匿名の「ネストされた」定義は、デバッグや内省がおそらく必要とされないほど非常に単純なメタクラスに対しては問題ないと合理的に主張できます。

Python 3では、「ネストされた定義」は機能しません。そこでは、のようにメタクラスをキーワード引数としてクラスに渡す必要があるため、本文でclass A(metaclass=Mcl):定義しても効果はありません。__metaclass__これは、Python 2コードのネストされたメタクラス定義がおそらく適切であるのは、コードをPython 3に移植する必要がないことが確実である場合にのみであると私は信じています(移植を非常に難しくしているため、目的のためにメタクラス定義をネスト解除します)-「使い捨て」コード、言い換えると、Python 3の一部のバージョンが速度、機能、またはサードの巨大で説得力のある利点を獲得する数年後には存在しません- Python 2.7(Python 2の最後のバージョン)を介したパーティのサポート。

コンピューティングの歴史が示すように、使い捨てになると予想されるコードには、あなたを完全に驚かせるという愛らしい習慣があり、まだ約20年後です(おそらく、「昔から」同じ時期に書いたコードは完全に忘れた;-)。これは確かに、メタクラスのネストされた定義を回避することを示唆しているように思われます。

于 2010-08-14T14:52:12.893 に答える