__slot__ 描述符在 python 中如何工作?
How do the __slot__ descriptors work in python?
我知道 __slots__
的作用和用途。
但是,关于使用 __slots__
创建的 member
描述符的底层机制如何工作,我还没有找到全面的答案。
对象级值实际存储在哪里?
有没有办法在不直接访问描述符的属性的情况下更改这些值?
(例如,当 class C
有 __dict__
你可以做 C.__dict__['key']
而不是 C.key
)
可以通过创建类似的 class 级描述符来 "extend" 定义 __slots__
对象的不变性吗?并作为对此的进一步阐述;可以使用 metaclasses 构建一个不可变对象,但不通过手动创建所述描述符来显式定义 __slots__
吗?
__slot__
属性在对象的本机内存表示中分配,然后访问的 class 关联的描述符实际上使用 CPython 中的本机 C 方法来设置并检索对 Python 对象的引用,这些对象属于 class 实例上的每个槽属性,作为 C 结构。
插槽的描述符,在 Python 中出现,名称为 member_descriptor
,定义如下:https://github.com/python/cpython/blob/master/Objects/descrobject.c
如果不使用 CTypes 与本机代码交互,您无论如何都无法从纯 Python 代码执行或增强这些描述符。
可以通过像
这样的方式获得他们的类型
class A:
__slots__ = "a"
member_descriptor = type(A.a)
然后人们可以假设它可以从它继承,并编写派生的 __get__
和 __set__
方法来进行检查等 - 但不幸的是,它不会像一个基地 class.
但是,可以编写其他并行的描述符,这些描述符又可以调用本机描述符来实际存储值。
通过使用元 class,可以在 class 创建时重命名传入的 __slots__
并将它们的访问包装在可以执行额外检查的自定义描述符中 - 甚至隐藏然后从 "dir".
因此,对于一个朴素的类型检查插槽变体元class,可以
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__]
我知道 __slots__
的作用和用途。
但是,关于使用 __slots__
创建的 member
描述符的底层机制如何工作,我还没有找到全面的答案。
对象级值实际存储在哪里?
有没有办法在不直接访问描述符的属性的情况下更改这些值?
(例如,当 class C
有 __dict__
你可以做 C.__dict__['key']
而不是 C.key
)
可以通过创建类似的 class 级描述符来 "extend" 定义 __slots__
对象的不变性吗?并作为对此的进一步阐述;可以使用 metaclasses 构建一个不可变对象,但不通过手动创建所述描述符来显式定义 __slots__
吗?
__slot__
属性在对象的本机内存表示中分配,然后访问的 class 关联的描述符实际上使用 CPython 中的本机 C 方法来设置并检索对 Python 对象的引用,这些对象属于 class 实例上的每个槽属性,作为 C 结构。
插槽的描述符,在 Python 中出现,名称为 member_descriptor
,定义如下:https://github.com/python/cpython/blob/master/Objects/descrobject.c
如果不使用 CTypes 与本机代码交互,您无论如何都无法从纯 Python 代码执行或增强这些描述符。
可以通过像
这样的方式获得他们的类型class A:
__slots__ = "a"
member_descriptor = type(A.a)
然后人们可以假设它可以从它继承,并编写派生的 __get__
和 __set__
方法来进行检查等 - 但不幸的是,它不会像一个基地 class.
但是,可以编写其他并行的描述符,这些描述符又可以调用本机描述符来实际存储值。
通过使用元 class,可以在 class 创建时重命名传入的 __slots__
并将它们的访问包装在可以执行额外检查的自定义描述符中 - 甚至隐藏然后从 "dir".
因此,对于一个朴素的类型检查插槽变体元class,可以
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__]