Метаклассы и __slots__?
Итак, я немного читаю о метаклассах в Python и как type()
three-argument alter-ego используется для динамического создания классов. Однако третий аргумент обычно представляет собой dict
, который инициализирует переменную класса __dict__
, которая должна быть создана.
Если я хочу динамически создавать классы на основе метакласса, который использует __slots__
вместо __dict__
, как я могу это сделать? Является ли type()
еще каким-то образом использованным наряду с переопределением __new__()
?
Как FYI, я знаю о правильном использовании для __slots__
, чтобы сохранить память при создании большого числа классов против злоупотребления ею для обеспечения формы безопасности типов.
Пример нормального (нового стиля) класса, который устанавливает __metaclass__
и использует __dict__
:
class Meta(type):
def __new__(cls, name, bases, dctn):
# Do something unique ...
return type.__new__(cls, name, bases, dctn)
class Foo(object):
__metaclass__ = Meta
def __init__(self):
pass
В приведенном выше примере вызывается type.__new__()
, а четвертый аргумент (который становится третьим при фактическом использовании) создает __dict__
в Foo
. Но если бы я хотел изменить Meta
, чтобы включить __slots__
, то у меня нет словаря для перехода к функции type()
__new__()
(насколько мне известно - я еще не тестировал это, просто размышляя и пытаясь найти какой-то сценарий использования).
Изменить: Быстрое, но непроверенное предположение состоит в том, чтобы взять значение значений, которые нужно поместить в переменные __slots__
, и передать его в type.__new__()
. Затем добавьте __init__()
в Meta
, который заполняет переменные __slots__
из dict. Хотя я не уверен, как этот dict достигнет __init__()
, потому что объявление __slots__
предотвращает создание __dict__
, если __dict__
не определено в __slots__
...
Ответы
Ответ 1
Вы не можете создать тип с непустым атрибутом __slots__. Что вы можете сделать, так это вставить атрибут __slots__ в новый класс dict, например:
class Meta(type):
def __new__(cls, name, bases, dctn):
dctn['__slots__'] = ( 'x', )
return type.__new__(cls, name, bases, dctn)
class Foo(object):
__metaclass__ = Meta
def __init__(self):
pass
Теперь Foo имеет атрибуты с прорезью:
foo = Foo()
foo.y = 1
бросает
AttributeError: 'Foo' object has no attribute 'y'
Ответ 2
dctn
в вашем примере метакласса - это словарь классов, а не словарь экземпляра. __slots__
заменяет словарь экземпляра. Если вы создадите два примера:
class Meta(type):
def __new__(cls, name, bases, dctn):
return type.__new__(cls, name, bases, dctn)
class Foo1(object):
__metaclass__ = Meta
class Foo2(object):
__metaclass__ = Meta
__slots__ = ['a', 'b']
Тогда:
>>> f1 = Foo1()
>>> f2 = Foo2()
>>> f1.__dict__ is Foo1.__dict__
False
>>> f2.__dict__
Traceback (most recent call last):
...
AttributeError: 'Foo2' object has no attribute '__dict__'
Ответ 3
Подробнее о слотах, определенных в метаклассах.
Если подкласс type
определяет непустую __slots__
, Python выбрасывает TypeError
из-за некоторого сложного материала, связанного с реализацией.
In [1]:
class Meta(type):
__slots__ = ('x')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-38-1b16edef8eca> in <module>()
----> 1 class Meta(type):
2 __slots__ = ('x')
TypeError: nonempty __slots__ not supported for subtype of 'type'
Empty __slots__
, с другой стороны, не производит никаких ошибок, но шов не имеет эффекта.
In [2]:
class Meta(type):
__slots__ = ()
class Foo(metaclass=Meta):
pass
type(Foo)
Out [2]:
__main__.Meta
In [3]:
Foo.y = 42
Foo.y
Out [3]:
42
In [4]:
Foo.__dict__
Out [4]:
mappingproxy({'y': 42, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__module__': '__main__'})
In [5]:
foo = Meta('foo', (), {})
type(foo).__slots__
Out [5]:
()
In [6]:
foo.x = 42
foo.x
Out [6]:
42
In [7]:
foo.__dict__
Out [7]:
mappingproxy({'__dict__': <attribute '__dict__' of 'foo' objects>, 'x': 42, '__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'foo' objects>})
In [8]:
# Testing on non-metaclasses. Just in case.
class Bar:
__slots__ = ()
b = Bar()
b.__dict__
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-54-843730c66f3f> in <module>()
3
4 b = Bar()
----> 5 b.__dict__
AttributeError: 'Bar' object has no attribute '__dict__'