クラスブロックは、辞書を作成し、メタクラスを呼び出してクラスオブジェクトを作成するための大まかな構文糖衣です。
これ:
class Foo(object):
__metaclass__ = FooMeta
FOO = 123
def a(self):
pass
あなたが書いたかのようにほとんど出てきます:
d = {}
d['__metaclass__'] = FooMeta
d['FOO'] = 123
def a(self):
pass
d['a'] = a
Foo = d.get('__metaclass__', type)('Foo', (object,), d)
名前空間の汚染がない場合のみ(実際には、メタクラスを判別するためにすべてのベースを検索することもありますが、メタクラスの競合があるかどうかもわかりませんが、ここでは無視しています)。
メタクラス__setattr__
は、そのインスタンスの1つ(クラスオブジェクト)に属性を設定しようとしたときに何が起こるかを制御できますが、それを行わないクラスブロック内では、ディクショナリオブジェクトに挿入しているため、dict
クラスは制御しますメタクラスではなく、何が起こっているのか。だからあなたは運が悪い。
Python 3.xを使用している場合を除きます!Python 3.xでは__prepare__
、メタクラスにclassmethod(またはstaticmethod)を定義できます。これは、メタクラスコンストラクターに渡される前に、クラスブロック内に設定された属性を累積するために使用されるオブジェクトを制御します。デフォルト__prepare__
では通常の辞書が返されるだけですが、キーの再定義を許可しないカスタムのdictのようなクラスを作成し、それを使用して属性を蓄積することができます。
from collections import MutableMapping
class SingleAssignDict(MutableMapping):
def __init__(self, *args, **kwargs):
self._d = dict(*args, **kwargs)
def __getitem__(self, key):
return self._d[key]
def __setitem__(self, key, value):
if key in self._d:
raise ValueError(
'Key {!r} already exists in SingleAssignDict'.format(key)
)
else:
self._d[key] = value
def __delitem__(self, key):
del self._d[key]
def __iter__(self):
return iter(self._d)
def __len__(self):
return len(self._d)
def __contains__(self, key):
return key in self._d
def __repr__(self):
return '{}({!r})'.format(type(self).__name__, self._d)
class RedefBlocker(type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
return SingleAssignDict()
def __new__(metacls, name, bases, sad):
return super().__new__(metacls, name, bases, dict(sad))
class Okay(metaclass=RedefBlocker):
a = 1
b = 2
class Boom(metaclass=RedefBlocker):
a = 1
b = 2
a = 3
これを実行すると、次のようになります。
Traceback (most recent call last):
File "/tmp/redef.py", line 50, in <module>
class Boom(metaclass=RedefBlocker):
File "/tmp/redef.py", line 53, in Boom
a = 3
File "/tmp/redef.py", line 15, in __setitem__
'Key {!r} already exists in SingleAssignDict'.format(key)
ValueError: Key 'a' already exists in SingleAssignDict
いくつかのメモ:
__prepare__
メタクラスのインスタンス(クラス)が存在する前に呼び出されるため、classmethod
またはである必要があります。staticmethod
type
まだ3番目のパラメータが実数dict
である必要があるため、を通常のパラメータに__new__
変換するメソッドが必要です。SingleAssignDict
- サブクラス化することもできますが、おそらく(2)を回避できたでしょうが、のような非基本的なメソッドが、のような基本的なメソッドのオーバーライドを尊重しない
dict
ため、これを行うのは本当に嫌いです。だから私は辞書をサブクラス化してラップすることを好みます。update
__setitem__
collections.MutableMapping
- 実際の
Okay.__dict__
オブジェクトは通常の辞書です。これは、必要な辞書の種類によって設定され、気難しいためtype
ですtype
。これは、クラスの作成後にクラス属性を上書きしても例外が発生しないことを意味します。クラスオブジェクトのディクショナリによって強制的に上書きされないようにしたい場合__dict__
は、スーパークラスの呼び出し後に属性を上書きできます。__new__
残念ながら、この手法はPython 2.xでは使用できません(チェックしました)。この__prepare__
メソッドは呼び出されません。これは、Python 2.xの場合と同様に、メタクラスは__metaclass__
クラスブロック内の特別なキーワードではなく、magic属性によって決定されます。これは、クラスブロックの属性を蓄積するために使用されるdictオブジェクトが、メタクラスが認識されるまでにすでに存在していることを意味します。
Python2と比較してください。
class Foo(object):
__metaclass__ = FooMeta
FOO = 123
def a(self):
pass
ほぼ同等であること:
d = {}
d['__metaclass__'] = FooMeta
d['FOO'] = 123
def a(self):
pass
d['a'] = a
Foo = d.get('__metaclass__', type)('Foo', (object,), d)
呼び出すメタクラスが辞書から決定される場合とPython3の場合。
class Foo(metaclass=FooMeta):
FOO = 123
def a(self):
pass
ほぼ同等であること:
d = FooMeta.__prepare__('Foo', ())
d['Foo'] = 123
def a(self):
pass
d['a'] = a
Foo = FooMeta('Foo', (), d)
使用する辞書がメタクラスから決定される場所。