如何强制子类具有 __slots__?

Ara*_*Fey 3 python inheritance metaprogramming class slots

我有一堂课__slots__

class A:
    __slots__ = ('foo',)
Run Code Online (Sandbox Code Playgroud)

如果我创建一个没有指定__slots__的子类,该子类将有一个__dict__

class B(A):
    pass

print('__dict__' in dir(B))  # True
Run Code Online (Sandbox Code Playgroud)

有什么办法可以防止B有一个__dict__而不必设置__slots__ = ()

MSe*_*ert 5

@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
Run Code Online (Sandbox Code Playgroud)

因此,具有此元类的每个类和子类(默认情况下)__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__",
Run Code Online (Sandbox Code Playgroud)

通过 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)
Run Code Online (Sandbox Code Playgroud)

并验证它是否按预期工作:

# 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
Run Code Online (Sandbox Code Playgroud)

正如评论中(由Aran-Fey)指出的,在del __slots__和 添加__dict__到之间存在差异__slots__

这两个选项之间有一个细微的区别:del __slots__不仅会给你的班级__dict__,而且还会给你一个__weakref__槽。