Ответ @ AKX почти правильный.Я думаю, __prepare__
и метакласс действительно способ решить эту проблему довольно легко.
Просто подведем итог:
- Если пространство имен класса содержит ключ
__slots__
после выполнения тела класса, тогда класс будет использовать __slots__
вместо __dict__
. - Можно ввести имена в пространство имен класса до того, как тело класса будет выполнено с использованием
__prepare__
.
Так что, если мы просто вернемсловарь, содержащий ключ '__slots__'
из __prepare__
, тогда класс (если ключ '__slots__'
не будет снова удален во время оценки тела класса) будет использовать __slots__
вместо __dict__
.Поскольку __prepare__
просто предоставляет начальное пространство имен, можно легко переопределить __slots__
или удалить их снова в теле класса.
Так что метакласс, который предоставляет __slots__
по умолчанию, будет выглядеть следующим образом:
class ForceSlots(type):
@classmethod
def __prepare__(metaclass, name, bases, **kwds):
# calling super is not strictly necessary because
# type.__prepare() simply returns an empty dict.
# But if you plan to use metaclass-mixins then this is essential!
super_prepared = super().__prepare__(metaclass, name, bases, **kwds)
super_prepared['__slots__'] = ()
return super_prepared
Таким образом, каждый класс и подкласс с этим метаклассом будет (по умолчанию) иметь пустой __slots__
в своем пространстве имен и, таким образом, создаст «класс со слотами» (за исключением того, что __slots__
удалены намеренно).
Просто чтобы проиллюстрировать, как это будет работать:
class A(metaclass=ForceSlots):
__slots__ = "a",
class B(A): # no __dict__ even if slots are not defined explicitly
pass
class C(A): # no __dict__, but provides additional __slots__
__slots__ = "c",
class D(A): # creates normal __dict__-based class because __slots__ was removed
del __slots__
class E(A): # has a __dict__ because we added it to __slots__
__slots__ = "__dict__",
, который проходит тесты, упомянутые в AKZs, отвечает:
assert "__dict__" not in dir(A)
assert "__dict__" not in dir(B)
assert "__dict__" not in dir(C)
assert "__dict__" in dir(D)
assert "__dict__" in dir(E)
И чтобы убедиться, что он работает должным образом:
# A has slots from A: a
a = A()
a.a = 1
a.b = 1 # AttributeError: 'A' object has no attribute 'b'
# B has slots from A: a
b = B()
b.a = 1
b.b = 1 # AttributeError: 'B' object has no attribute 'b'
# C has the slots from A and C: a and c
c = C()
c.a = 1
c.b = 1 # AttributeError: 'C' object has no attribute 'b'
c.c = 1
# D has a dict and allows any attribute name
d = D()
d.a = 1
d.b = 1
d.c = 1
# E has a dict and allows any attribute name
e = E()
e.a = 1
e.b = 1
e.c = 1
Как указано в комментарии ( Aran-Fey ), существует разница между del __slots__
и добавлением __dict__
к __slots__
:
Существует небольшая разница между этими двумя вариантами: del __slots__
даст вашему классу не только __dict__
, но и слот __weakref__
.