13. 内置对象实现
13. 内置对象实现
内置对象是 Python 核心类型背后的具体数据结构。它们是普通的 Python 对象,因为它们具有标识、类型、引用计数、支持的属性以及由类型槽定义的行为。它们很特别,因为它们的存储和操作是直接用 C 实现的。
本章给出了一个大致的地图。后面的章节将深入探讨字符串、列表、元组、字典、集合、数字、函数、模块和框架。
13.1 内置函数是类型对象
内置类型,例如list, dict, 或者int本身就是一个Python对象。python print(type(list)) # <class 'type'> print(type(dict)) # <class 'type'> print(type(int)) # <class 'type'> 实例指向其类型对象。python xs = [1, 2, 3] print(type(xs)) # <class 'list'> C级:```text
xs ---> PyListObject
ob_refcnt
ob_type ----> PyList_Type
ob_size
ob_item
allocated
## 13.2 为什么用 C 实现内置函数
内置类型位于 Python 执行最热门的路径上。
常见的操作包括:```text
integer arithmetic
string hashing
attribute lookup
dictionary lookup
list append
tuple creation
function calls
iteration
exception creation
```如果这些操作作为普通 Python 代码实现,解释器将必须执行更多字节码来执行其自己的基本操作。 CPython 通过在 C 中实现核心类型来避免这种情况。
例如,Python 字典用于:```text
module globals
class namespaces
object attributes
keyword arguments
import caches
annotations
many user data structures
```慢速字典会使整个解释器变慢。
## 13.3 常见的内置对象模式
大多数内置对象实现都遵循以下形式:```c
typedef struct {
PyObject_HEAD
/* type-specific fields */
} SomeObject;
```或者,对于可变大小的对象:```c
typedef struct {
PyObject_VAR_HEAD
/* type-specific fields */
} SomeVarObject;
```然后类型对象提供槽:```c
PyTypeObject Some_Type = {
.tp_name = "...",
.tp_basicsize = sizeof(SomeObject),
.tp_dealloc = ...,
.tp_repr = ...,
.tp_as_number = ...,
.tp_as_sequence = ...,
.tp_as_mapping = ...,
.tp_methods = ...,
};
```这种模式在 CPython 中重复出现。
## 13.4 对象族
主要的内置对象族有:
|家庭|示例 |主要角色|
| ----------------------- | ------------------------------------------- | -------------------------------- |
|数字对象 |`int`, `float`, `complex`, `bool`|算术和数字协议|
|文本和二进制对象|`str`, `bytes`, `bytearray`, `memoryview`|文本、二进制数据、缓冲区 |
|序列对象 |`list`, `tuple`, `range`|订购系列 |
|映射对象|`dict`, `mappingproxy`|键值存储|
|设置对象|`set`, `frozenset`|基于哈希的成员资格 |
|可调用对象|函数、方法、内置函数 |调用|
|运行时对象 |模块、框架、代码、回溯 |执行机械|
|描述符对象 |属性、getset、成员、方法描述符 |属性行为 |
|迭代器对象 |列表迭代器、字典迭代器、生成器 |迭代 |
|异常对象 |`BaseException`和子类 |错误传播|
每个系列都使用相同的对象模型,但针对不同的操作进行优化。
## 13.5 整数对象
蟒蛇`int`值是任意精度。```python
x = 10**100
print(x)
```CPython 将整数存储为`PyLongObject`,一个可变大小的对象。它不会对所有 Python 整数使用一种固定的机器整数。
从概念上讲:```text
PyLongObject
PyVarObject header
ob_size = number of internal digits, with sign encoded
digits[]
```小整数使用很少的内部数字。大整数使用很多数字。
这解释了为什么Python整数不会像C那样溢出`long`在普通算术中。```python
x = 2**1000
print(x * x)
```成本随着整数大小的增加而增加。小整数的运算速度很快。对非常大的整数进行运算需要多精度算术。
## 13.6 布尔对象`bool`是一个子类`int`。```python
print(isinstance(True, int)) # True
print(True + True) # 2
```恰好有两个布尔单例对象:```python
True
False
```在 C 级别,这些是运行时拥有的对象。代码应该在 Python 级别将布尔值与真值进行比较,而不是通过构造新的布尔实例。```python
if condition:
...
```C 代码通常返回带有宏的布尔值,例如:```c
Py_RETURN_TRUE;
Py_RETURN_FALSE;
```## 13.7 浮点对象
蟒蛇`float`通常作为 C double 实现。
从概念上讲:```text
PyFloatObject
PyObject header
double value
```浮动对象是固定大小的。```python
x = 1.5
y = 2.25
print(x + y)
```浮点运算遵循平台的浮点行为,通常在常见系统上遵循 IEEE 754 双精度。
浮点数存储近似的二进制实数。它并不精确地表示小数。```python
print(0.1 + 0.2)
```令人惊讶的结果来自二进制浮点表示,而不是来自 Python 特定的算术。
## 13.8 复杂对象
蟒蛇`complex`存储两个浮点值:```text
PyComplexObject
PyObject header
real double
imag double
```例子:```python
z = 1.5 + 2.0j
print(z.real)
print(z.imag)
```复数参与数字槽。它们支持算术,但不支持排序比较,例如`<`。```python
1 + 2j < 3 + 4j # TypeError
```## 13.9 字符串对象
蟒蛇`str`存储 Unicode 文本。
字符串是不可变的。```python
s = "hello"
t = s.upper()
upper()创建另一个字符串。它不会变异s。
CPython 的 Unicode 实现针对紧凑存储进行了优化。内部表示可以根据字符串中最大的代码点使用不同的元素宽度。
从概念上讲:text PyUnicodeObject object header length hash cache kind compact/ascii flags character data 重要的字符串优化包括:```text
cached hash value
compact layout
ASCII fast path
interning for selected strings
specialized Unicode operations
## 13.10 字节和字节数组`bytes`是不可变的二进制数据。```python
b = b"hello"
bytearray是可变的二进制数据。```python
buf = bytearray(b"hello")
buf[0] = ord("H")
|类型 |可变 |主要用途|
| ----------- | ------: | -------------------- |
|`bytes`| 没有 |不可变的二进制数据 |
|`bytearray`| 是的 |可变二进制缓冲区 |
两者都是 0 到 255 范围内整数的类序列对象。```python
b = b"abc"
print(b[0]) # 97
```## 13.11 列表对象
列表是一个可变序列。```python
xs = [1, 2, 3]
xs.append(4)
```CPython 列表对象存储一个指向单独分配的对象引用数组的指针。
从概念上讲:```text
PyListObject
PyVarObject header
ob_size = logical length
ob_item ----> array of PyObject *
allocated = capacity
```该数组存储引用,而不是内联对象数据。```text
list
ob_item[0] ---> int object 1
ob_item[1] ---> int object 2
ob_item[2] ---> int object 3
```列表在增长时过度分配。这使得重复`append`平均效率高。
## 13.12 元组对象
元组是一个不可变的序列。```python
t = (1, 2, 3)
```元组将项目引用内联存储在元组分配中。
从概念上讲:```text
PyTupleObject
PyVarObject header
ob_size = length
ob_item[0]
ob_item[1]
ob_item[2]
```元组在创建后不能更改长度。这使得内联存储变得实用。
元组不变性是指元组的引用,不一定是所包含对象的深层可变性。```python
t = ([],)
t[0].append(1)
print(t) # ([1],)
```该元组仍然指向同一个列表。名单发生了变化。
## 13.13 字典对象
字典是一个将键映射到值的哈希表。```python
d = {"name": "Ada", "age": 36}
```字典在整个 CPython 中使用,而不仅仅是在用户代码中。
他们存储:```text
module globals
class namespaces
instance attributes
keyword arguments
import caches
```字典查找大致需要:```text
hash the key
find a matching table slot
compare keys if needed
return associated value
```重要属性:```text
average O(1) lookup
insertion order preservation
hash-based key storage
resize when table becomes too full
specialized layouts for object attributes
```字典是 CPython 中对性能最敏感的对象之一。
## 13.14 设置和冻结设置对象
集合是没有值的键的哈希表。```python
seen = set()
seen.add("x")
```freezeset 是不可变的。```python
s = frozenset(["a", "b"])
```集合针对成员资格测试进行了优化:```python
if item in seen:
...
```内部结构在本质上与 dict 类似,但仅存储元素。
集合操作包括:```text
union
intersection
difference
symmetric difference
subset testing
membership testing
frozenset如果所有元素都是可哈希的,则它是可哈希的,因此它可以用作字典键或集合元素。
13.15 范围对象
一个range表示一个算术级数,但不存储每个元素。python r = range(0, 1_000_000, 2) 从概念上讲:text range object start stop step length 即使对于很大的范围,该物体也是紧凑的。```python
import sys
print(sys.getsizeof(range(10))) print(sys.getsizeof(range(10**12)))
## 13.16 函数对象
Python 函数对象包装可执行代码和运行时上下文。```python
def add(a, b):
return a + b
```函数对象包含对以下对象的引用:```text
code object
globals dictionary
defaults
keyword defaults
closure cells
annotations
qualified name
module name
```从概念上讲:```text
PyFunctionObject
code
globals
defaults
kwdefaults
closure
annotations
name
qualname
```代码对象包含字节码。函数对象提供执行该字节码所需的环境。
## 13.17 代码对象
代码对象是编译后的可执行元数据。
它包含:```text
bytecode
constants
names
local variable names
free variables
cell variables
stack size
flags
line table
exception table
filename
function name
```例子:```python
def f(x):
return x + 1
code = f.__code__
print(code.co_consts)
print(code.co_varnames)
```代码对象是不可变的。它们可以被多个函数对象共享。
## 13.18 模块对象
模块对象代表导入的模块。```python
import math
print(math)
```模块主要包含命名空间字典。
从概念上讲:```text
module object
name
dict
spec
loader
package
file
```模块字典存储模块定义的全局变量。```python
import math
print(math.__dict__["sqrt"])
```导入模块会创建或检索模块对象并将其存储在`sys.modules`。
## 13.19 类和实例对象
类是类型对象。实例是类型指针指向类的对象。```python
class User:
pass
u = User()
```从概念上讲:```text
User
type object
attributes and methods
base classes
MRO
u
instance object
ob_type ---> User
instance dictionary or slots
```普通实例通常将属性存储在字典中。```python
u.name = "Ada"
```和`__slots__`,实例可以将选定的字段存储在固定偏移量而不是字典中。```python
class Point:
__slots__ = ("x", "y")
```这会减少每个实例的内存,并可以加快某些属性访问模式的速度。
## 13.20 方法对象
当通过实例访问函数时,Python 会创建一个绑定方法对象。```python
class C:
def f(self):
return 1
c = C()
m = c.f
```绑定方法存储:```text
function
self object
```从概念上讲:```text
bound method
__func__ ---> C.f
__self__ ---> c
```呼唤`m()`通过`c`作为第一个参数。
这是描述符行为。函数对象的描述符槽执行绑定。
## 13.21 内置函数和方法对象
一些可调用函数直接用 C 实现。
示例:```python
len
print
dict.get
list.append
```这些是内置函数或方法对象。它们包装 C 函数指针和元数据。
它们比同等的 Python 级函数更快,因为它们避免为操作本身执行 Python 字节码。
内置方法,例如`list.append`仍然接收 Python 对象并在内部遵循引用所有权规则。
## 13.22 迭代器对象
迭代器对象实现`__iter__`和`__next__`。```python
it = iter([1, 2, 3])
print(next(it))
```列表迭代器存储:```text
reference to list
current index
```字典迭代器存储:```text
reference to dict
iteration position
version or mutation state
```生成器也是迭代器,但它们更复杂,因为它们包含挂起的执行帧。
## 13.23 生成器对象
生成器对象表示暂停的函数执行。```python
def count():
yield 1
yield 2
```呼唤`count()`不立即运行函数体。它创建一个生成器对象。```python
g = count()
```生成器存储执行状态:```text
code or frame state
instruction position
locals
evaluation stack
exception state
closed/running state
```每个`next(g)`恢复执行直到下一个`yield`或返回。
生成器将对象模型与解释器框架模型连接起来。
## 13.24 框架对象
框架对象表示正在执行或挂起的代码块。
框架包含:```text
code object
globals
builtins
locals
value stack
instruction pointer
exception state
previous frame link where exposed
```框架是为函数调用、模块执行、类体执行、生成器、协程和回溯创建的。
框架对象对于以下方面很重要:```text
debuggers
profilers
trace functions
exceptions
inspect module
generators and coroutines
```它们也足够昂贵,以至于 CPython 努力避免实现完整的 Python 可见框架对象,除非在某些路径中需要。
## 13.25 回溯对象
回溯对象记录异常传播的位置。```python
try:
1 / 0
except ZeroDivisionError as exc:
tb = exc.__traceback__
```回溯链接到:```text
frame
line number or instruction position
next traceback
```回溯可以保留帧。框架可以留住当地人。这意味着异常可以使大型对象图保持活动状态。
这是长时间运行的程序中常见的内存保留模式。
## 13.26 异常对象
异常是派生自的普通对象`BaseException`。```python
try:
raise ValueError("bad")
except ValueError as exc:
print(exc.args)
```异常对象可以存储:```text
args
message data
__cause__
__context__
__traceback__
notes
custom attributes
```异常类是普通类,但异常传播已深度集成到解释器中。
## 13.27 描述符对象
描述符控制属性访问。
内置描述符对象类型包括:```text
function descriptors
method descriptors
member descriptors
getset descriptors
wrapper descriptors
property objects
classmethod objects
staticmethod objects
```描述符定义以下一项或多项:```python
__get__
__set__
__delete__
```描述符实现:```text
methods
properties
slots
C-level members
C-level computed attributes
special method wrappers
```如果没有描述符,Python 的方法绑定和属性模型的灵活性就会大大降低。
## 13.28 内存视图对象
一个`memoryview`暴露另一个对象的缓冲区而不进行复制。```python
b = bytearray(b"hello")
v = memoryview(b)
```内存视图使导出的缓冲区保持活动状态,并让代码根据可变性读取或写入内存。
这对于跨字节对象和扩展模块的零复制操作至关重要。
MemoryView 对象参与缓冲区生存期规则。导出器不得以导致活动视图无效的方式释放内存或调整内存大小。
## 13.29 胶囊对象
胶囊包装了一个 C 指针,以便通过 Python API 进行安全交换。
C 扩展使用胶囊来公开本机指针,而不使它们成为普通的 Python 对象。
从概念上讲:```text
capsule
void *pointer
name
destructor
context
```胶囊对于 C 扩展互操作性很有用。它们允许一个扩展模块发布另一个扩展可以导入的 C API。
## 13.30 对象实现权衡
内置对象实现平衡了多种压力:
|压力|效果|
| ------------- | ------------------------------------------------- |
|速度|用于热操作的专用 C 路径 |
|内存使用 |紧凑布局、共享、实习、免费列表 |
|兼容性 |稳定的 Python 语义和 C API 行为 |
|可调试性|运行时检查、调试构建、内省钩子 |
|便携性|避免破坏受支持平台的假设 |
|可扩展性|插槽、协议、子类化支持 |
|安全|引用计数、GC遍历、错误处理 |
许多 CPython 实现细节都来自于这些权衡。
例如,列表过度分配可以提高追加速度,但可能会保留额外的内存。字典插入顺序会消耗内存,但会提供有用的语言行为。引用计数可以迅速销毁,但需要循环 GC 和仔细的 C API 所有权规则。
## 13.31 心智模型
使用这个模型:```text
built-in type
C struct for instance layout
PyTypeObject for behavior
slots for protocols
methods for public operations
deallocator for owned references
optional GC traversal for cycles
```在阅读内置类型实现时,询问:```text
What does the object store?
Does it own Python references?
Is it fixed-size or variable-size?
Does it use auxiliary memory?
Does it participate in cyclic GC?
What slots does its type object fill?
What operations are hot paths?
What invariants must always hold?
```## 13.32 总结
内置对象是 Python 核心运行时值的专门 C 实现。它们都遵循相同的对象模型:公共标头、类型指针、特定于类型的存储、引用所有权规则以及由类型槽定义的行为。
它们的实现经过优化,因为它们几乎位于每个 Python 程序的下面。列表、元组、字典、字符串、函数、模块、框架和异常不仅仅是库的便利。它们是口译员的工作部件。