メタクラスの具体的な使用例は何ですか? 質問する

メタクラスの具体的な使用例は何ですか? 質問する

メタクラスを使うのが好きで、定期的にメタクラスを解決策として提案してくれる友人がいます。

私は、メタクラスを使用する必要はほとんどないと考えています。なぜでしょうか? クラスに対してそのようなことを行うのであれば、おそらくオブジェクトに対して行うべきだと思うからです。そして、小さな再設計/リファクタリングが必要です。

メタクラスを使用できるようになったことで、多くの場所で多くの人がクラスをある種の二流オブジェクトとして使用するようになりましたが、これは私にとっては悲惨なことのように思えます。プログラミングはメタプログラミングに置き換えられるのでしょうか? 残念ながら、クラス デコレータの追加により、メタプログラミングはさらに受け入れられるようになりました。

ですので、Python のメタクラスの有効な (具体的な) 使用例をぜひ知りたいです。あるいは、クラスを変更する方がオブジェクトを変更するよりも優れている場合がある理由を知りたいです。

始めようと思います:

サードパーティのライブラリを使用する場合、特定の方法でクラスを変更できると便利な場合があります。

(これは私が思いつく唯一のケースであり、具体的ではありません)

ベストアンサー1

最近、同じ質問を受け、いくつかの答えを思いつきました。言及されたユースケースのいくつかについて詳しく説明し、新しいユースケースをいくつか追加したいので、このスレッドを復活させてもよろしいでしょうか。

私が見たほとんどのメタクラスは、次の 2 つのうちのいずれかを実行します。

  1. 登録(データ構造へのクラスの追加):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass
    

    サブクラス化するたびにModel、そのクラスがmodels辞書に登録されます。

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}
    

    これはクラス デコレータを使用して実行することもできます。

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass
    

    または明示的な登録関数を使用する場合:

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)
    

    実際、これはほぼ同じです。クラス デコレータを好ましくないと言及していますが、実際には、クラスでの関数呼び出しに対する単なる構文上の糖衣に過ぎないので、魔法のようなことはありません。

    いずれにせよ、この場合のメタクラスの利点は継承であり、メタクラスは任意のサブクラスで機能しますが、他のソリューションは明示的に装飾または登録されたサブクラスでのみ機能します。

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
    
  2. リファクタリング (クラス属性の変更または新しい属性の追加):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass
    

    サブクラス化Modelして属性を定義するたびにField、属性の名前が挿入され (たとえば、より詳しいエラー メッセージを表示するため)、辞書にグループ化されます_fields(すべてのクラス属性とそのすべての基本クラスの属性を毎回調べる必要がなくなり、反復処理が容易になります)。

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}
    

    これも、クラス デコレータを使用して (継承なしで) 実行できます。

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(
    

    あるいは明示的に:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!
    

    しかし、読みやすく保守しやすい非メタプログラミングを主張するあなたの主張とは反対に、これははるかに扱いにくく、冗長で、エラーが発生しやすくなります。

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}
    

最も一般的で具体的な使用例を考慮すると、メタクラスを絶対に使用する必要がある唯一のケースは、クラス名または基本クラスのリストを変更する場合です。これは、一度定義されると、これらのパラメーターはクラスに焼き付けられ、デコレータや関数ではそれらを解除できないためです。

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

これは、類似した名前や不完全な継承ツリーを持つクラスが定義されるたびに警告を発行するフレームワークでは役立つかもしれませんが、トロール以外にこれらの値を実際に変更する理由は思いつきません。おそらく David Beazley ならできるでしょう。

とにかく、Python 3 では、メタクラスには メソッドもあり__prepare__、これを使用すると、クラス本体を 以外のマッピングに評価できるためdict、順序付けされた属性、オーバーロードされた属性、その他の非常に便利な機能がサポートされます。

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

 

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

順序付けられた属性は作成カウンターで実現でき、オーバーロードはデフォルト引数でシミュレートできると主張するかもしれません。

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

 

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

見た目がはるかに醜いだけでなく、柔軟性も低くなります。整数や文字列などの順序付きリテラル属性が必要な場合はどうでしょうか。 がNoneの有効な値である場合はどうなるでしょうかx

最初の問題を解決する創造的な方法は次のとおりです。

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

2 番目の問題を解決する創造的な方法は次のとおりです。

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

しかし、これは単純なメタクラス (特に最初のメタクラスは本当に頭が混乱します) よりもはるかに魔法的です。私の言いたいことは、メタクラスはなじみがなく直感に反するものと見なしますが、プログラミング言語の進化の次のステップと見なすこともできるということです。考え方を変えるだけでいいのです。結局のところ、関数ポインターを持つ構造体を定義し、それをその関数の最初の引数として渡すことを含め、C ですべてを行うことができます。C++ を初めて見る人は、「これは何の魔法ですか? コンパイラーはなぜthisメソッドに暗黙的に渡すのに、通常の関数や静的関数には渡さないのですか? 引数については明示的かつ詳細に説明したほうがよいでしょう」と言うかもしれません。しかし、オブジェクト指向プログラミングは、理解してしまえばはるかに強力です。これは、ええと、準アスペクト指向プログラミングだと思います。メタクラスを理解してしまえば、実際には非常に単純なので、都合の良いときに使用しないのはなぜでしょうか。

そして最後に、メタクラスは素晴らしいです。プログラミングは楽しいものであるべきです。標準的なプログラミング構造とデザイン パターンを常に使用するのは退屈で刺激がなく、想像力を妨げます。少しは人生を楽しみましょう。ここに、あなただけのためのメタメタクラスがあります。

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan

編集

これはかなり古い質問ですが、まだ賛成票が集まっているので、より包括的な回答へのリンクを追加しようと思いました。メタクラスとその使用法についてもっと知りたい場合は、それに関する記事を公開しました。ここ

おすすめ記事