如何强制子类具有__slots__?

时间:2019-06-13 11:22:46

标签: python class inheritance metaprogramming slots

我有一个__slots__的班级:

class A:
    __slots__ = ('foo',)

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

class B(A):
    pass

print('__dict__' in dir(B))  # True

是否有任何方法可以防止B拥有__dict__而不必设置__slots__ = ()

2 个答案:

答案 0 :(得分:4)

@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__",

哪个通过了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__之间是有区别的:

  

这两个选项之间有微小的区别:del __slots__不仅会给您的班级一个__dict__,还会给一个__weakref__位置。

答案 1 :(得分:0)

这样的元类和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 –继承类的实例可能总是比基类本身重一点?