如何强制子类具有 __slots__?
How can I force subclasses to have __slots__?
我有一个 class 和 __slots__
:
class A:
__slots__ = ('foo',)
如果我创建一个子class而不指定__slots__
,子class将有一个__dict__
:
class B(A):
pass
print('__dict__' in dir(B)) # True
有什么方法可以防止 B
在不设置 __slots__ = ()
的情况下得到 __dict__
吗?
像这样的 metaclass 和 the __prepare__()
hook 怎么样?
import sys
class InheritSlots(type):
def __prepare__(name, bases, **kwds):
# this could combine slots from bases, I guess, and walk the base hierarchy, etc
for base in bases:
if base.__slots__:
kwds["__slots__"] = base.__slots__
break
return kwds
class A(metaclass=InheritSlots):
__slots__ = ("foo", "bar", "quux")
class B(A):
pass
assert A.__slots__
assert B.__slots__ == A.__slots__
assert "__dict__" not in dir(A)
assert "__dict__" not in dir(B)
print(sys.getsizeof(A()))
print(sys.getsizeof(B()))
出于某种原因,这仍然会打印 64, 88
– 也许继承 class 的实例总是比基础 class 本身重一点?
的答案几乎是正确的。我认为 __prepare__
和 metaclass 确实是很容易解决这个问题的方法。
简单回顾一下:
- 如果 class 的命名空间在执行 class 主体后包含
__slots__
键,则 class 将使用 __slots__
而不是 __dict__
.
- 可以在 class 的命名空间中注入名称,然后 class 主体通过使用
__prepare__
执行。
因此,如果我们只是 return 一个包含来自 __prepare__
的键 '__slots__'
的字典,那么 class 将会(如果 '__slots__'
键不是在评估 class 主体期间再次删除)使用 __slots__
而不是 __dict__
。
因为 __prepare__
只是提供了初始命名空间,所以可以轻松覆盖 __slots__
或在 class 主体中再次删除它们。
所以默认情况下提供 __slots__
的元 class 看起来像这样:
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
所以每个 class 和 subclass 与这个 metaclass 将(默认情况下)在它们的命名空间中有一个空的 __slots__
并因此创建一个 "class with 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__",
通过了 AKZ 回答中提到的测试:
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__
之间是有区别的:
There's a minor difference between the two options: del __slots__
will give your class not only a __dict__
, but also a __weakref__
slot.
我有一个 class 和 __slots__
:
class A:
__slots__ = ('foo',)
如果我创建一个子class而不指定__slots__
,子class将有一个__dict__
:
class B(A):
pass
print('__dict__' in dir(B)) # True
有什么方法可以防止 B
在不设置 __slots__ = ()
的情况下得到 __dict__
吗?
像这样的 metaclass 和 the __prepare__()
hook 怎么样?
import sys
class InheritSlots(type):
def __prepare__(name, bases, **kwds):
# this could combine slots from bases, I guess, and walk the base hierarchy, etc
for base in bases:
if base.__slots__:
kwds["__slots__"] = base.__slots__
break
return kwds
class A(metaclass=InheritSlots):
__slots__ = ("foo", "bar", "quux")
class B(A):
pass
assert A.__slots__
assert B.__slots__ == A.__slots__
assert "__dict__" not in dir(A)
assert "__dict__" not in dir(B)
print(sys.getsizeof(A()))
print(sys.getsizeof(B()))
出于某种原因,这仍然会打印 64, 88
– 也许继承 class 的实例总是比基础 class 本身重一点?
__prepare__
和 metaclass 确实是很容易解决这个问题的方法。
简单回顾一下:
- 如果 class 的命名空间在执行 class 主体后包含
__slots__
键,则 class 将使用__slots__
而不是__dict__
. - 可以在 class 的命名空间中注入名称,然后 class 主体通过使用
__prepare__
执行。
因此,如果我们只是 return 一个包含来自 __prepare__
的键 '__slots__'
的字典,那么 class 将会(如果 '__slots__'
键不是在评估 class 主体期间再次删除)使用 __slots__
而不是 __dict__
。
因为 __prepare__
只是提供了初始命名空间,所以可以轻松覆盖 __slots__
或在 class 主体中再次删除它们。
所以默认情况下提供 __slots__
的元 class 看起来像这样:
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
所以每个 class 和 subclass 与这个 metaclass 将(默认情况下)在它们的命名空间中有一个空的 __slots__
并因此创建一个 "class with 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__",
通过了 AKZ 回答中提到的测试:
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__
之间是有区别的:
There's a minor difference between the two options:
del __slots__
will give your class not only a__dict__
, but also a__weakref__
slot.