簡體   English   中英

如何強制子類具有__slots__?

[英]How can I force subclasses to have __slots__?

我有一個__slots__類:

class A:
    __slots__ = ('foo',)

如果我在沒有指定__slots__情況下創建子類,則子類將具有__dict__

class B(A):
    pass

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

有沒有辦法阻止B擁有__dict__而不必設置__slots__ = ()

@AKX的答案幾乎是正確的。 我認為__prepare__和元類確實是很容易解決的問題。

回顧一下:

  • 如果類的名稱空間在執行類主體后包含__slots__鍵,則該類將使用__slots__而不是__dict__
  • 使用__prepare__執行類主體之前 ,可以將名稱注入類的名稱空間。

因此,如果我們只是從__prepare__返回一個包含鍵'__slots__'的字典,那么該類將(如果在評估類體時沒有再次刪除'__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

正如在注釋中指出(由阿蘭-的Fcγ )之間有一個差del __slots__和添加__dict____slots__

這兩個選項之間存在細微差別: del __slots__會為您的類提供__dict__ ,還會為__weakref__一個槽。

像這樣的元類和__prepare__()鈎子怎么樣?

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 - 也許一個繼承類的實例總是比基類本身重一點?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM