我知道__slots__做什么,应该用来做什么。
然而,对于使用member创建的__slots__描述符的底层机制如何工作,我还没有找到一个全面的答案。
对象级值实际存储在哪里?
有没有办法在不直接访问描述符属性的情况下更改这些值?
(例如,当类C__dict__时,可以执行C.__dict__['key']而不是C.key
可以通过创建类似的类级描述符来“扩展”定义__slots__的对象的不变性吗?作为这一点的进一步阐述,是否可以使用元类来构建一个不可变的对象,而不是通过手动创建所述描述符来显式地定义__slots__

最佳答案

__slot__属性在对象的本机内存表示中分配,然后与访问的类相关联的描述符实际使用C Python中的本机C方法来设置和检索对Python对象的引用,该对象被作为C结构分配给类实例上的每个slot属性。
用python表示的名为member_descriptor的slots的描述符在这里定义:https://github.com/python/cpython/blob/master/Objects/descrobject.c
如果不使用CTypes与本机代码交互,无论如何都无法从纯Python代码中执行或增强这些描述符。
有可能通过做一些类似的事情来达到他们的类型

class A:
   __slots__ = "a"

member_descriptor = type(A.a)

然后我们可以假设可以从它继承,并编写派生的__get____set__方法来执行cheking等操作,但不幸的是,它不能作为基类工作。
但是,可以编写其他并行的描述符,这些描述符可以反过来调用本机描述符来实际存储值。
通过使用元类,可以在类创建时重命名传入的__slots__,并将其访问包装在可以执行额外检查的自定义描述符中,甚至可以从“dir”中隐藏。
所以,对于一个天真的类型检查slots变量元类,可以有
class TypedSlot:
    def __init__(self, name, type_):
        self.name = name
        self.type = type_

    def __get__(self, instance, owner):
        if not instance:
            return self
        return getattr(instance, "_" + self.name)

    def __set__(self, instance, value):
        if not isinstance(value, self.type):
            raise TypeError
        setattr(instance, "_" + self.name, value)


class M(type):
    def __new__(metacls, name, bases, namespace):
        new_slots = []
        for key, type_ in namespace.get("__slots__", {}).items():
            namespace[key] = TypedSlot(key, type_)
            new_slots.append("_" + key)
        namespace["__slots__"] = new_slots
        return super().__new__(metacls, name, bases, namespace)

    def __dir__(cls):
        return [name for name in super().__dir__() if  name not in cls.__slots__]

10-06 05:21
查看更多