为什么使用描述符可以引入 __slots__?

Why the introduction of __slots__ was made possible with descriptors?

在系列 "The History of Python" 的 this blog post 中,Guido van Rossum 指出:

Another enhancement made possible with descriptors was the introduction of the __slots__ attribute on classes.

我对这句话的理解是:under the hood __slots__ is implemented by descriptors.

但与我的解释相反,Guido van Rossum 在后面写了几行:

Underneath the covers, the implementation of this feature is done entirely in C and is highly efficient.

那么,__slots__不是描述符实现的吗?

但是两句话之后,他又写道:

Not only was __slots__ an interesting application of descriptors, ...

那么,__slots__ 和描述符的实际情况如何?

__slots__ 是否由描述符实现?如果是:如何?

考虑一个简单的 class:

class A:
    __slots__ = ('a',)

什么是a?这是一个描述符:

>>> type(A.a)
<class 'member_descriptor'>

__slots__ 值中的每个字符串用于创建具有 member_descriptor 值的 class 属性。

这意味着您可以(尝试)通过 A.a.__get__

访问它
>>> a = A()
>>> a.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: a

A.a.__set__

分配给它
>>> a.a = 7

然后尝试再次访问它:)

>>> a.a
7

不能做的是尝试分配给实例上的任何其他属性:

>>> A.b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'A' has no attribute 'b'
>>> a.b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'b'
>>> a.b = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'b'

__slots__ 的存在不仅创建请求的 class 属性,而且 阻止 在实例上创建任何其他属性。

这些说法并不自相矛盾。 __slots__ 定义的属性是创建的 class 上的描述符 该描述符的实现是用 C 编写的(假设 CPython)。

描述符 class 被称为 member_descriptor ,如以下示例代码所示:

import inspect

class Test:
    __slots__ = 'a',
    def __init__(self, a):
        self.a = a

type(Test.a)                        # member_descriptor
inspect.isdatadescriptor(Test.a)    # True
inspect.ismemberdescriptor(Test.a)  # True

在 GitHub 上的 CPython 存储库中快速搜索显示 the C implementation of it (Link for CPython version 3.8.0)


更详细一点:

A Python class 本质上是 dict 带有(很多)花里胡哨的东西。另一方面,有 Python-C-classes 使用 C-struct 来实现 Python class。这样的 C 结构比字典更快并且需要(显着)更少的内存,即使它只包含 Python 个对象(它基本上是一个包含对 Python 个对象的引用的 C 数组)。

为了使 "normal" Python classes 可以从更快的访问和减少的内存占用中受益,引入了 __slots__。 class 和 __slots__ 本质上将被转换为 C 结构。然而,为了使属性 lookup/setting/deletion 映射到相应的 struct 成员成为可能,需要某种翻译层(描述符)。 __slots__ 中定义的成员的转换层是 member_descriptor

因此,当您在 __slots__-class 的实例上查找属性时,您将得到 member_descriptor 并且 member_descriptor 将知道如何 get/set/delete底层C成员-struct.