12. 对象布局和类型槽

CPython 将每个运行时值表示为一个对象。每个对象都有一个内存布局,每个对象的类型描述了如何解释该内存。

对象布局答案:text What fields exist inside this object? Where are the references to other Python objects? How large is one instance? Does the object have variable-sized trailing storage? Does the object participate in cyclic garbage collection? 类型槽答案:```text How is this object called? How is it deallocated? How does attribute lookup work? How does indexing work? How does addition work? How does iteration work? How is it represented as text?


## 12.1 对象内存以标头开始

每个普通的 CPython 对象都以对象头开始。

从概念上讲:```c
typedef struct {
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;
```这为每个对象提供了两个基本字段:

|领域|意义|
| ----------- | ---------------------------- |
|`ob_refcnt`|引用计数 |
|`ob_type`|指向对象类型的指针 |

具体对象将其自己的字段放置在该标头之后。

形状示例:```text
PyLongObject
    PyObject header
    integer-specific fields

PyListObject
    PyObject / PyVarObject header
    list-specific fields

PyFunctionObject
    PyObject header
    function-specific fields
```因为每个对象都以相同的标头开头,所以通用 CPython 代码可以通过以下方式操作未知对象:`PyObject *`

## 12.2 固定大小的对象布局

固定大小对象的每个实例都具有相同的 C 结构体大小。

例子:```c
typedef struct {
    PyObject_HEAD
    double value;
} FloatLikeObject;
```记忆形状:```text
+--------------------+
| ob_refcnt          |
+--------------------+
| ob_type            |
+--------------------+
| value              |
+--------------------+
```该类型的所有实例都具有相同的大小。

大多数固定大小的对象结构的示例:```text
float
function
module
cell
method
weakref
many iterator objects
many descriptor objects
```固定大小并不意味着该对象没有对外部存储的引用。函数对象具有固定大小的结构,但它指向代码对象、全局字典、默认元组、闭包元组、注释和其他对象。

## 12.3 可变大小对象布局

可变大小对象使用大小字段扩展公共标头。

从概念上讲:```c
typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size;
} PyVarObject;
```扩展类型使用:```c
typedef struct {
    PyObject_VAR_HEAD
    PyObject *items[1];
} ArrayLikeObject;
```记忆形状:```text
+--------------------+
| ob_refcnt          |
+--------------------+
| ob_type            |
+--------------------+
| ob_size            |
+--------------------+
| variable payload   |
+--------------------+

ob_size具有特定于类型的含义。

类型 尺寸字段的含义
tuple 元素数量
bytes 字节数
int 内部数字的数量和符号
自定义变量类型 由类型实现定义

12.4 内联存储与间接存储

可变大小的 Python 值可以内联或间接存储数据。

元组将项目引用内联存储在同一分配中:text tuple object header size = 3 item[0] ---> object A item[1] ---> object B item[2] ---> object C 列表存储指向单独项目数组的指针:```text list object header size = 3 ob_item ----+ allocated | v [ptr A][ptr B][ptr C][spare...]


元组在分配后无法更改大小,因此内联存储非常高效。

列表必须增长和收缩,因此它将元素保存在一个单独的数组中,可以重新分配该数组,而无需移动列表对象本身。

## 12.5 类型对象描述实例布局

每个对象都指向一个类型对象。

类型对象存储布局元数据,例如:```text
tp_basicsize
tp_itemsize
tp_dictoffset
tp_weaklistoffset
tp_flags
```重要字段:

|领域|意义|
| ------------------- | -------------------------------------------------- |
|`tp_basicsize`|实例的固定大小 |
|`tp_itemsize`|每个变量尾随项的大小 |
|`tp_dictoffset`|实例的偏移量`__dict__`,如果有的话 |
|`tp_weaklistoffset`|弱引用列表的偏移量(如果支持)|
|`tp_flags`|描述功能的类型标志 |

对于固定大小类型:```text
tp_basicsize = sizeof(MyObject)
tp_itemsize = 0
```对于可变大小类型:```text
tp_basicsize = fixed header and fields
tp_itemsize = size of one trailing element
```然后分配计算:```text
total bytes = tp_basicsize + n * tp_itemsize
```这就是 CPython 分配长度元组的方式`n`在一个内存块中。

## 12.6 类型对象描述行为

类型对象还存储行为。

简化视图:```text
PyTypeObject
    name
    size fields
    base type
    method table
    member table
    getset table
    deallocator
    repr function
    call function
    attribute functions
    numeric slots
    sequence slots
    mapping slots
    iterator slots
``` Python 计算时:```python
x + y
```CPython 通过类型槽进行调度。

 Python 计算时:```python
x[i]
```CPython 使用序列或映射槽。

 Python 计算时:```python
x()
```CPython 使用调用槽。

类型对象既是布局描述符又是调度表。

## 12.7 主要类型插槽类别`PyTypeObject`包含很多字段。重要的类别是:

|老虎机类别 |目的|
| -------------------- | -------------------------------------------------------- |
|生命周期槽 |分配、初始化、释放|
|代表插槽 |`repr`, `str`|
|属性槽 |获取、设置、描述符行为 |
|呼叫槽|函数调用语法 |
|号码槽 |算术和位运算|
|序列槽 |长度、索引、包含、连接 |
|映射插槽|字典式查找和赋值|
|迭代器槽 |迭代协议|
|缓冲槽 |原始内存暴露|
| GC插槽|遍历和清理|
|子类化插槽 |继承和MRO行为|

每个类别都将 Python 语法或运行时行为映射到 C 函数指针。

## 12.8 生命周期槽

生命周期槽控制创建和销毁。

重要槽位:```text
tp_new
tp_init
tp_alloc
tp_dealloc
tp_free
```典型的创作路径:```text
call class
    tp_new allocates or returns object
    tp_init initializes object
    return object
```例子:```python
obj = MyClass(1, 2)
```从概念上讲:```text
MyClass.__call__
    MyClass.__new__
    MyClass.__init__
``` C 级别,它流经类型槽。

对于不可变对象,`tp_new`通常构造完整值,因为对象创建后无法修改。

对于可变对象,`tp_init`分配后可以填充或重置字段。

## 12.9 释放槽`tp_dealloc`销毁引用计数为零的对象。

一个简单的解除分配器:```c
static void
Box_dealloc(BoxObject *self)
{
    Py_XDECREF(self->value);
    Py_TYPE(self)->tp_free((PyObject *)self);
}
```释放器必须释放该对象拥有的每个Python引用。

对于 GC 感知类型,释放器还必须在中断引用之前取消跟踪对象:```c
static void
Box_dealloc(BoxObject *self)
{
    PyObject_GC_UnTrack(self);
    Py_CLEAR(self->value);
    Py_TYPE(self)->tp_free((PyObject *)self);
}
```必须以防御性方式编写解除分配器。`Py_DECREF``Py_CLEAR`可以通过终结器间接执行任意Python代码。

## 12.10 代表时段

表示槽支持`repr()``str()`

重要槽位:```text
tp_repr
tp_str
```Python代码:```python
repr(obj)
str(obj)
```映射到类型行为。

示例类型实现草图:```c
static PyObject *
Counter_repr(CounterObject *self)
{
    return PyUnicode_FromFormat("Counter(%ld)", self->value);
}
```然后:```python
repr(counter)
```可以生产:```text
Counter(10)
```如果`tp_str`不存在,CPython 可能会回退到`tp_repr`或通用对象格式化行为,具体取决于类型。

## 12.11 属性访问槽

属性访问是 CPython 最重要的路径之一。

相关槽位:```text
tp_getattro
tp_setattro
tp_getattr
tp_setattr
```现代类型通常使用`tp_getattro``tp_setattro`

Python代码:```python
obj.name
obj.name = value
del obj.name
```通过属性机制流动。

对于普通对象,通用属性查找处理:```text
data descriptors
instance dictionary
non-data descriptors
class attributes
base classes
__getattr__
```自定义类型可以通过提供自定义属性槽来覆盖此行为。

## 12.12 Descriptor Slots

Descriptors implement binding behavior.

A descriptor type can define:```text
tp_descr_get
tp_descr_set
```Python 级别的等效项:```python
__get__
__set__
__delete__
```函数是描述符。当函数存储在类中并通过实例访问时,描述符逻辑会创建绑定方法。```python
class Counter:
    def inc(self):
        return 1

c = Counter()
m = c.inc
```从概念上讲:```text
look up inc on Counter
find function object
function descriptor binds self = c
return bound method
```描述符实现方法、属性、类方法、静态方法、槽和许多内置属性。

## 12.13 调用槽

调用槽支持函数调用语法。

相关槽位:```text
tp_call
```Python代码:```python
obj(a, b, c)
```要求对象的类型是可调用的。

函数、类、方法、内置函数和对象`__call__`所有人都参与该协议。

对于用户定义的类:```python
class F:
    def __call__(self, x):
        return x + 1

f = F()
print(f(10))
```在类型级别,类机制确保实例具有`__call__`表现为可调用对象。

## 12.14 号码槽

数字槽处理算术和位运算。

他们住在一个`PyNumberMethods`桌子。

常见概念槽:```text
nb_add
nb_subtract
nb_multiply
nb_remainder
nb_power
nb_negative
nb_positive
nb_absolute
nb_bool
nb_invert
nb_lshift
nb_rshift
nb_and
nb_xor
nb_or
nb_int
nb_float
nb_index
```Python语法:```python
a + b
a - b
a * b
-a
bool(a)
a & b
a << b
```使用数字协议机制。

对于用户定义的类,特殊方法如`__add__`, `__bool__` `__index__`连接到这些插槽。

## 12.15 二元运算调度

二元运算需要仔细调度,因为两个操作数有两种类型。

为了:```python
a + b
```CPython 可能会考虑:```text
left operand type
right operand type
subclass relationships
left slot
right reflected slot
NotImplemented result
```Python级别的方法:```python
__add__
__radd__
__iadd__
```映射到内部二进制运算机器。

类型可以返回`NotImplemented`让另一个操作数参与。

例子:```python
class A:
    def __add__(self, other):
        return NotImplemented

class B:
    def __radd__(self, other):
        return "handled by B"

print(A() + B())
```这种调度行为是 Python 数据模型的一部分,是通过类型槽实现的。

## 12.16 序列槽

序列槽位于`PySequenceMethods`桌子。

常见概念槽:```text
sq_length
sq_concat
sq_repeat
sq_item
sq_ass_item
sq_contains
sq_inplace_concat
sq_inplace_repeat
```Python语法:```python
len(x)
x[i]
x[i] = value
item in x
x + y
x * n
```可以使用序列槽。

列表、元组、字符串、字节和范围都公开序列行为,尽管它们的内部布局不同。

## 12.17 映射槽

映射槽位于`PyMappingMethods`桌子。

常见概念槽:```text
mp_length
mp_subscript
mp_ass_subscript
```Python语法:```python
len(x)
x[key]
x[key] = value
del x[key]
```可以使用映射槽。

字典是主要的映射类型。

用户定义的对象实现`__getitem__`, `__setitem__` `__len__`可以通过类型槽集成参与类似映射的行为。

## 12.18 序列与映射查找

相同的语法可以表示序列或映射访问:```python
x[i]
```对于一个列表,`i`是一个索引。

对于一个字典来说,`i`是一把钥匙。

类型决定如何解释操作。

从概念上讲:```text
list.__getitem__(index)
    index must be integer-like

dict.__getitem__(key)
    key may be any hashable object
``` C 级别,CPython 根据类型的映射和序列槽进行调度。

## 12.19 迭代器槽

迭代使用类型级协议支持。

相关槽位:```text
tp_iter
tp_iternext
```Python代码:```python
for item in obj:
    ...
```大致意思是:```text
iterator = iter(obj)
while true:
    try:
        item = next(iterator)
    except StopIteration:
        break
```C级:```text
tp_iter
    returns iterator object

tp_iternext
    returns next item or signals StopIteration
```迭代器返回自身`tp_iter`

容器返回一个单独的迭代器对象。

## 12.20 缓冲槽

缓冲区协议将原始内存公开给其他对象而不进行复制。

相关槽位面积:```text
tp_as_buffer
```对象如`bytes`, `bytearray`, `memoryview`、数组和数值扩展类型都可以参与。

缓冲协议对于以下方面很重要:```text
zero-copy I/O
binary parsing
NumPy interop
memoryview
file and socket operations
serialization
```当消费者持有视图时,缓冲区导出器必须保持内存有效。

这使得缓冲区槽成为对象模型和内存生命周期模型的一部分。

## 12.21 垃圾收集槽

GC感知的容器类型需要遍历和清除支持。

相关槽位:```text
tp_traverse
tp_clear

tp_traverse报告包含对收集器的 Python 引用。```c static int Box_traverse(BoxObject *self, visitproc visit, void *arg) { Py_VISIT(self->value); return 0; }


`tp_clear`循环收集期间包含引用的中断。```c
static int
Box_clear(BoxObject *self)
{
    Py_CLEAR(self->value);
    return 0;
}
```如果某个类型可以参与循环但没有正确实现这些循环则它可能会泄漏无法到达的循环

## 12.22 成员表

一些 C 扩展字段可以通过成员定义公开

概念模式:```c
static PyMemberDef Point_members[] = {
    {"x", T_LONG, offsetof(PointObject, x), 0, "x coordinate"},
    {"y", T_LONG, offsetof(PointObject, y), 0, "y coordinate"},
    {NULL}
};
```类型对象指向该表。```c
.tp_members = Point_members
```这允许 CPython  C 结构体字段公开为 Python 属性

成员表对于简单的 C 字段很有用更复杂的属性通常使用 getset 描述符

## 12.23 Getset 表

Getset 表公开计算属性

概念模式:```c
static PyObject *
Point_get_norm(PointObject *self, void *closure)
{
    double norm = sqrt(self->x * self->x + self->y * self->y);
    return PyFloat_FromDouble(norm);
}

static PyGetSetDef Point_getset[] = {
    {"norm", (getter)Point_get_norm, NULL, "vector norm", NULL},
    {NULL}
};
```type对象指向getset表:```c
.tp_getset = Point_getset
```getset 条目的行为类似于描述符

Python代码:```python
p.norm
```调用吸气剂

## 12.24 方法表

C 扩展方法通过方法表公开

概念模式:```c
static PyObject *
Counter_inc(CounterObject *self, PyObject *Py_UNUSED(ignored))
{
    self->value += 1;
    return PyLong_FromLong(self->value);
}

static PyMethodDef Counter_methods[] = {
    {"inc", (PyCFunction)Counter_inc, METH_NOARGS, "increment counter"},
    {NULL}
};
```类型对象指向方法表:```c
.tp_methods = Counter_methods
```Python代码:```python
counter.inc()
```使用描述符和方法调用机制来调用 C 函数

## 12.25 堆类型和静态类型

CPython 有静态类型和堆类型

静态类型被定义为 C 全局结构许多内置类型历史上都使用静态类型对象

堆类型在运行时动态分配用户定义的 Python 类是堆类型

比较

|类型种类 |分配 |典型用途|
| ----------- | ------------------ | ------------------------------------------- |
|静态型| C静态存储|内置和扩展定义的类型 |
|堆类型|运行时分配 | Python 许多现代扩展类型 |

堆类型更加灵活它们支持正常的类行为例如动态属性子类化元数据和运行时管理的生命周期

现代扩展代码通常更喜欢通过模块定义槽的堆类型,`PyType_FromSpec`。

## 12.26`PyType_FromSpec`

`PyType_FromSpec`根据声明性规范创建类型对象

概念草图:```c
static PyType_Slot Counter_slots[] = {
    {Py_tp_dealloc, Counter_dealloc},
    {Py_tp_methods, Counter_methods},
    {0, NULL}
};

static PyType_Spec Counter_spec = {
    .name = "example.Counter",
    .basicsize = sizeof(CounterObject),
    .itemsize = 0,
    .flags = Py_TPFLAGS_DEFAULT,
    .slots = Counter_slots,
};
```然后:```c
PyObject *type = PyType_FromSpec(&Counter_spec);
```这种风格避免直接初始化一个大的`PyTypeObject`struct 并与不断发展的 CPython 内部结构配合得更好

## 12.27 槽继承

子类继承基类的行为除非它们重写它。```python
class A:
    def __len__(self):
        return 10

class B(A):
    pass

print(len(B()))

B继承长度行为A

在 C 级别,类型准备填充继承的槽并计算最终的类型布局和方法解析元数据。

插槽继承必须尊重:```text base class layout method resolution order descriptor behavior special method lookup type flags GC support memory offsets


## 12.28 特殊方法查找

特殊方法查找通常会绕过正常的实例查找。

例子:```python
class X:
    pass

x = X()
x.__len__ = lambda: 3

len(x)
```这仍然引发`TypeError`因为`len(x)`查找类型上的长度行为,而不是实例字典中的长度行为。

正确的:```python
class X:
    def __len__(self):
        return 3

x = X()
print(len(x))
```此规则允许 CPython 优化协议操作并保留内置语法的一致行为。

## 12.29 布局冲突

多重继承可能会造成布局冲突。

如果两个基类需要不兼容的 C 级实例布局,CPython 可能会拒绝该类。

形状示例:```text
BaseA requires C layout A
BaseB requires C layout B
Derived(BaseA, BaseB)
    cannot combine both layouts safely
``` Python 类通常很灵活,因为它们的实例状态是基于字典或基于槽的。

C 扩展类型有更严格的布局要求。

这是扩展类型设计应该对继承保守的原因之一,除非明确需要子类化行为。

## 12.30 心智模型

读取类型代码时使用此模型:```text
object memory stores state
type object describes layout and behavior
slots map Python operations to C functions
protocols are implemented through slot tables
attribute lookup uses descriptors and dictionaries
numeric, sequence, and mapping syntax dispatch through specialized slots
GC-aware objects must expose references through traversal slots
deallocation must release owned references and free memory
```Python 表达式通常是通过类型槽的路径。```python
result = obj[key] + other
```从概念上讲:```text
load obj
dispatch subscription through mapping or sequence slot
load other
dispatch addition through number slots
store result
```## 12.31 总结

对象布局定义了 CPython 对象的内存形状。类型槽定义对象支持哪些操作。`PyObject``PyVarObject`提供通用标头。具体结构添加特定于类型的字段。`PyTypeObject`记录大小、偏移量、标志、生命周期挂钩、属性行为、协议槽、方法表、成员表、getset 表和垃圾收集支持。

这种设计允许 CPython 将统一的对象表示与内置和扩展类型的高度专业化的实现相结合。