Ответ 1
Блок класса является примерно синтаксическим сахаром для создания словаря, а затем вызывает метакласс для создания объекта класса.
Это:
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__
может контролировать, что происходит, когда вы пытаетесь установить атрибут в одном из его экземпляров (объект класса), но внутри блока классов, что вы этого не делаете, вы вставляете в объект словаря, поэтому класс dict
контролирует, что происходит, а не ваш метакласс. Так что вам не повезло.
Если вы не используете Python 3.x! В Python 3.x вы можете определить метод __prepare__
classmethod (или staticmethod) в метаклассе, который контролирует, какой объект используется для накопления атрибутов, установленных в блоке класса, прежде чем они будут переданы конструктору метакласса. По умолчанию __prepare__
просто возвращает нормальный словарь, но вы можете создать пользовательский диктоподобный класс, который не позволяет переопределять ключи и использовать их для накопления ваших атрибутов:
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
все еще нуждается в том, чтобы его третий параметр был реальнымdict
, поэтому у вас должен быть метод__new__
, который преобразуетSingleAssignDict
в обычный - Я мог бы подклассифицировать
dict
, который, вероятно, избежал бы (2), но мне очень не нравится это делать из-за того, как неосновные методы, такие какupdate
, не уважают ваши переопределения основных методов, таких как__setitem__
. Поэтому я предпочитаю подклассcollections.MutableMapping
и завершать словарь. - Фактический
Okay.__dict__
объект является нормальным словарем, потому что он был установленtype
иtype
является тонким в отношении того типа словаря, который он хочет. Это означает, что переписывание атрибутов класса после создания класса не вызывает исключения. Вы можете перезаписать атрибут__dict__
после вызова суперкласса в__new__
, если вы хотите сохранить отмену без перезаписи с помощью словаря класса объектов.
К сожалению, этот метод недоступен в Python 2.x(я проверил). Метод __prepare__
не вызывается, что имеет смысл, так как в Python 2.x метакласс определяется магическим атрибутом __metaclass__
, а не специальным ключевым словом в classblock; что означает, что объект dict, используемый для накопления атрибутов для блока классов, уже существует к тому времени, когда известен метакласс.
Сравнить Python 2:
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)
Если метаклас для вызова определяется из словаря, по сравнению с Python 3:
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)
Если словарь для использования определяется из метакласса.