私は最近同じ質問をされ、いくつかの答えを思いつきました。言及されたユースケースのいくつかについて詳しく説明し、いくつかの新しいユースケースを追加したかったので、このスレッドを復活させても問題ないことを願っています.
私が見たほとんどのメタクラスは、次の 2 つのいずれかを行います。
登録 (データ構造へのクラスの追加):
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 :(
リファクタリング (クラス属性の変更または新しい属性の追加):
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
これは、似たような名前のクラスや不完全な継承ツリーが定義されているときに警告を発行するフレームワークで役立つかもしれませんが、これらの値を実際に変更するためにトローリングする以外の理由は考えられません. デビッド・ビーズリーならできるかもしれません。
とにかく、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
編集
これはかなり古い質問ですが、まだ支持を得ているので、より包括的な回答へのリンクを追加すると思いました. メタクラスとその使用法について詳しく知りたい場合は、ここで記事を公開しました。