7.Python对象模型
7.Python 对象模型
Python 对象模型是 CPython 的基础。 Python 中运行的所有内容最终都会成为对对象的操作:整数、字符串、列表、模块、函数、类、异常、框架,甚至编译后的代码。
在语言层面,Python 表示每个对象都有一个标识、一个类型和一个值。对象标识在创建后保持固定,is比较身份,并且id()返回表示该身份的整数。 ([Python 文档][1])
CPython 使用 C 结构、对象头、引用计数、类型对象和操作槽来实现此模型。
7.1 对象、值和身份
Python 对象具有三个核心属性。
| 物业 | 意义 | 示例 |
|---|---|---|
| 身份 | 对象的稳定身份 | id(x) |
| 类型 | 对象的运行时类型 | type(x) |
| 价值 | 对象所表示的数据 | 42, "abc", [1, 2] |
例子:```python x = [1, 2, 3] y = x
print(x is y) # True print(type(x)) # <class 'list'> print(x) # [1, 2, 3]
`x`和`y`是绑定到同一个对象的两个名称。他们有相同的身份。```python
x.append(4)
print(y) # [1, 2, 3, 4]
```名单发生了变化。绑定未创建副本。
在 CPython 中,对象标识与普通对象的对象地址紧密相关。该语言仅承诺稳定的身份,而不是身份必须是内存地址。
## 7.2 名称绑定到对象
Python 变量是绑定,而不是存储盒。
这段代码:```python
a = 10
b = a
```不将整数值复制到`b`。它绑定`b`到引用的同一个对象`a`。
对于不可变对象,这通常感觉就像是值复制:```python
a = 10
b = a
a = 20
print(b) # 10
```但对象`10`没有改变。名称`a`被反弹到另一个物体上。
对于可变对象,差异是可见的:```python
a = []
b = a
a.append("x")
print(b) # ['x']
```名称`a`和名字`b`请参阅同一个列表。通过一个引用进行的变异可以通过另一个引用看到。
这种名称到对象模型是 CPython 的核心。字节码指令主要加载对象引用、存储对象引用、传递对象引用以及对对象引用的调用操作。
## 7.3 每个运行时值都是一个`PyObject *`在 CPython 内部,对象通常通过类型指针进行处理`PyObject *`。官方 C API 文档指出,每个指向 Python 对象的指针都可以转换为`PyObject *`,并且正常发布版本存储引用计数和指向基础对象结构中相应类型对象的指针。 ([Python 文档][2])
从概念上讲:```c
typedef struct {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;
```这是简化的,但它捕获了基本模型。
每个对象都以共享标头开头:```text
+--------------------+
| reference count |
+--------------------+
| type pointer |
+--------------------+
| object-specific |
| payload |
+--------------------+
```对于整数,有效负载存储整数位。
对于列表,有效负载存储大小信息和指向元素引用数组的指针。
对于函数,有效负载存储代码对象、全局变量、默认值、闭包单元和相关元数据。
公共标头使解释器可以在顶层统一处理所有对象。
## 7.4 固定大小和可变大小对象
CPython 区分固定大小对象和可变大小对象。
固定大小的对象使用基础对象标头。
可变大小的对象包含一个附加的大小字段。 C API 文档描述了`PyVarObject`作为延伸`PyObject`这增加了一个`ob_size`可变大小对象的字段。 ([Python 文档][2])
从概念上讲:```c
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size;
} PyVarObject;
```元组的大小是可变的,因为长度为 2 的元组和长度为 100 的元组需要不同的存储。
字节对象的大小是可变的。
长整数也是可变大小的,因为 CPython 使用数字序列存储任意精度整数。
大概形状:```text
PyObject
used by many fixed-size objects
PyVarObject
used by objects whose logical size is known at allocation time
```示例:
|对象类型|标题类型 |原因 |
| ----------- | ------------------------ | | ------------------------------------------- |
|`float` | `PyObject`|固定大小的有效负载|
|`tuple` | `PyVarObject`|元素数量各不相同 |
|`bytes` | `PyVarObject`|字节数各不相同 |
|`int` | `PyVarObject`|整数位数不同 |
|`str`|专门的可变布局|字符串长度不同 |
## 7.5 对象类型定义行为
对象的类型决定了它支持哪些操作。 Python 的数据模型在语言级别定义了这一点:对象具有类型,类型决定支持的操作。 ([Python 文档][1])
在 CPython 级别,对象头中的类型指针指向`PyTypeObject`。
从概念上讲:```text
object
ob_refcnt
ob_type --------> type object
name
size
base classes
method table
number slots
sequence slots
mapping slots
call slot
attribute slots
```当 Python 计算时:```python
x + y
```CPython 不寻找独立的`add`功能。它询问相关类型的机制,加法对于这些对象是如何工作的。
对于整数,加法使用特定于整数的代码。
对于字符串,加法使用字符串连接。
对于列表,加法会创建一个串联列表。
对于用户定义的类,加法可以调用`__add__`。
语法是统一的。实现是类型导向的。
## 7.6 类型是对象
类型本身就是一个对象。```python
print(type(42)) # <class 'int'>
print(type(int)) # <class 'type'>
print(type(type)) # <class 'type'>
```这种递归结构是有意为之的。`int`是一个对象。`list`是一个对象。
用户定义的类是对象。
大多数类型对象的类型是`type`。```python
class User:
pass
print(type(User)) # <class 'type'>
print(type(User())) # <class '__main__.User'>
```这解释了为什么可以动态地分配、传递、存储、装饰和创建类。```python
def make_class():
class Item:
pass
return Item
C = make_class()
obj = C()
```类语句创建一个类对象。它将类名绑定到该对象。
## 7.7 对象布局和类型布局
CPython 对象的布局必须与其类型对象期望的布局相匹配。
CPython 源代码注释在`Include/object.h`请注意,对象是通过以下方式访问的`PyObject *`,并且对象大小在分配后不会更改,因为移动或调整对象大小需要更新引用。 ([GitHub][3])
这个限制很重要。
列表可以增长,但列表对象本身不会扩展以直接容纳所有元素。相反,它拥有一个可以重新分配的单独元素数组。
元组无法增长。它的项目引用作为元组分配的一部分存储。
从概念上讲:```text
list object
header
current length
allocated capacity
pointer to item array ---> [PyObject*, PyObject*, PyObject*, ...]
tuple object
header
length
inline item references ---> [PyObject*, PyObject*, PyObject*, ...]
```这种差异解释了为什么在元组大小固定的情况下列表追加可以有效地摊销。
## 7.8 可变性
可变性意味着对象的值可以改变,但其身份保持不变。```python
xs = [1, 2]
before = id(xs)
xs.append(3)
after = id(xs)
print(before == after) # True
```列表对象仍然是相同的对象。其内容发生了变化。
不可变对象不会公开改变其值的操作。```python
s = "abc"
t = s.upper()
print(s) # abc
print(t) # ABC
```字符串操作返回另一个对象。
常见的可变和不可变对象:
|可变 |不可变 |
| -------------------- | ---------------------------------------------------------- |
|`list` | `int` |
| `dict` | `float` |
| `set` | `str` |
| `bytearray` | `bytes`|
|大多数类实例 |`tuple`,如果其包含的引用是固定的 |
元组作为容器是不可变的,但它可以包含可变对象:```python
t = ([],)
t[0].append(1)
print(t) # ([1],)
```The tuple still points to the same list.名单发生了变化。
## 7.9 Reference Semantics
大多数 CPython 运行时操作都会移动对对象的引用,而不是对象本身。
Function calls pass object references.```python
def add_item(xs):
xs.append(1)
items = []
add_item(items)
print(items) # [1]
```该函数接收对同一列表对象的引用。
重新绑定本地名称不会影响调用者:```python
def replace(xs):
xs = [1, 2, 3]
items = []
replace(items)
print(items) # []
```名称`xs`函数里面是反弹。原始列表没有发生变化。
这种区别对于 API 设计很重要。改变对象和重新绑定局部变量是不同的操作。
## 7.10 属性
对象可以公开属性。```python
class User:
pass
u = User()
u.name = "Ada"
print(u.name)
```对于普通的用户定义对象,实例属性通常存储在实例字典中。
从概念上讲:```text
u
type pointer ---> User
__dict__ -----> {"name": "Ada"}
```属性查找比直接字典查找更复杂。 CPython 必须考虑:```text
data descriptors
instance dictionary
non-data descriptors
class attributes
base classes
__getattribute__
__getattr__
method binding
```例子:```python
class User:
species = "human"
u = User()
u.name = "Ada"
print(u.name) # instance attribute
print(u.species) # class attribute
```如果实例中不存在该属性,CPython 将搜索该类及其基类。
## 7.11 方法是描述符
通过实例访问时,存储在类中的函数的行为类似于方法。```python
class Counter:
def inc(self):
return 1
c = Counter()
print(c.inc)
c.inc是一个绑定方法。它将函数对象与实例结合起来c。
这种行为来自描述符协议。
描述符是定义以下一项或多项的对象:python __get__(self, obj, objtype=None) __set__(self, obj, value) __delete__(self, obj) 功能实现__get__,因此从实例检索时它们会自动绑定。
概念查找:```text Counter.inc raw function object
c.inc bound method: function = Counter.inc self = c
## 7.12 特殊方法和槽
Python 语法映射到特殊方法。
|语法 |特殊方法|
| ---------| -------------- |
|`x + y` | `__add__` |
| `x[i]` | `__getitem__` |
| `x()` | `__call__` |
| `len(x)` | `__len__` |
| `iter(x)` | `__iter__` |
| `next(x)` | `__next__` |
| `x in y` | `__contains__` |
| `str(x)` | `__str__` |
| `repr(x)` | `__repr__`|
在 CPython 级别,其中许多操作对应于`PyTypeObject`。
例如,类型可以提供号码时隙、序列时隙、映射时隙和呼叫时隙。
这意味着 Python 代码如下:```python
len(obj)
```不只是简单地调用`obj.__len__()`通过正常的实例查找。 CPython 使用类型级协议机制。这使得常见操作更快、更一致。
## 7.13 内置类型是具有特权实现的普通对象
内置类型参与相同的对象模型,但它们的实现位于 C 中。```python
print(type([])) # <class 'list'>
print(type({})) # <class 'dict'>
print(type("abc")) # <class 'str'>
```官方内置类型文档将主要内置类型分组为数字、序列、映射、类、实例和异常。 ([Python 文档][4])
之间的区别`list`而用户定义的类并不是一个是对象,另一个不是。两者都是对象。不同之处在于`list`有一个具有固定内部布局和专用插槽的 C 实现。
Python 列表对象包含实现细节,例如:```text
object header
logical length
allocated capacity
pointer to element storage
```字典包含哈希表实现。
字符串包含特定于 Unicode 的布局和缓存的元数据。
这些内部布局针对 CPython 的运行时行为进行了优化。
## 7.14 类和实例
类定义了其实例共享的行为。```python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(10, 20)
```运行时:```text
Point
class object
type: type
attributes:
__init__
...
p
instance object
type: Point
attributes:
x = 10
y = 20
```调用该类会调用构造机械:```text
Point(10, 20)
call type object
allocate instance through __new__
initialize instance through __init__
return instance
```这就是为什么可以定制构造:```python
class OnlyOne:
def __new__(cls):
print("allocate")
return super().__new__(cls)
def __init__(self):
print("initialize")
__new__创建或返回一个对象。__init__初始化它。
7.15 继承和方法解析
Python 支持通过类进行继承。```python class Animal: def speak(self): return "?"
class Dog(Animal):
def speak(self):
return "woof"
当解析实例上的属性时,CPython 根据类方法解析顺序进行搜索,通常称为 MRO。python
print(Dog.mro)
对于单一继承:text
Dog
Animal
object
对于多重继承,Python 使用 C3 线性化。python
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.mro)
## 7.16 属性查找顺序
对于正常表达式:```python
obj.name
```CPython 执行结构化查找。
简化订单:```text
1. Look for data descriptors on the type or its bases.
2. Look in the instance dictionary.
3. Look for non-data descriptors or other class attributes.
4. If still missing, call __getattr__ if defined.
5. Otherwise raise AttributeError.
```数据描述符定义`__set__`或者`__delete__`。
非数据描述符仅定义`__get__`。
这解释了为什么`property`可以覆盖实例字典条目:```python
class User:
@property
def name(self):
return "computed"
u = User()
print(u.name)
```该属性是一个数据描述符。它胜过普通实例存储。
## 7.17 对象创建
对象创建通常有两个阶段。```text
allocation
reserve memory for the object
initialization
set the initial object state
```在Python级别:```python
obj = cls.__new__(cls)
cls.__init__(obj)
```在普通代码中,调用该类会执行这两个步骤。
在 C 级别,类型对象通过槽控制分配和初始化,例如:```text
tp_new
tp_init
tp_alloc
tp_dealloc
```这种分离很重要,因为不可变对象在创建过程中需要它们的值。
例如,元组或整数不能被创建为空,然后通过公共操作自由变异为其最终值。它们的最终价值是在分配或建设期间确定的。
## 7.18 对象销毁
CPython 中的对象销毁主要是引用计数驱动的。
当对象的引用计数达到零时,CPython 会调用其释放函数。
从概念上讲:```text
Py_DECREF(obj)
decrement reference count
if reference count == 0:
call type-specific deallocator
```释放器释放对象拥有的引用,释放辅助内存,并将对象的内存返回给分配器。
对于容器,释放可以级联:```python
xs = [[1], [2], [3]]
del xs
```删除外部列表会减少对内部列表的引用。如果这些内部列表没有其他引用,它们也会被销毁。
循环需要循环垃圾回收,因为仅靠引用计数无法回收使彼此保持活动状态的对象。
## 7.19 平等和同一性
同一性和平等是不同的。```python
a = [1, 2]
b = [1, 2]
print(a == b) # True
print(a is b) # False
==询问对象比较是否相等。is询问两个引用是否指向同一个对象。
在 CPython 级别:```text is compare object pointers
==
dispatch rich comparison through type machinery
自定义类可以定义相等性:python
class Point:
def init(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return (
isinstance(other, Point)
and self.x == other.x
and self.y == other.y
)
## 7.20 哈希
哈希支持字典键和集合元素。
用作字典键的对象在保留在字典中时必须具有稳定的哈希值。```python
d = {}
d["name"] = "Ada"
```字符串是可散列的,因为它们是不可变的。
列表不可散列,因为它们的内容可以更改:```python
hash("abc") # works
hash((1, 2)) # works
hash([1, 2]) # TypeError
```自定义类可以定义:```python
__hash__
__eq__
```该规则很实用:如果相等性可以改变,则哈希表就可以破坏。可变对象通常应该避免基于值的哈希。
## 7.21 容器存储参考
Python 容器存储对象的引用。```python
xs = [object(), object(), object()]
```该列表存储对三个对象的引用。它不会内联完整的对象数据。
从概念上讲:```text
list
items[0] ---> object A
items[1] ---> object B
items[2] ---> object C
```这解释了浅复制行为:```python
a = [[1], [2]]
b = a.copy()
b[0].append(99)
print(a) # [[1, 99], [2]]
```外部列表已复制。内部列表对象是共享的。
深复制递归地复制包含的对象:```python
import copy
a = [[1], [2]]
b = copy.deepcopy(a)
```## 7.22 对象协议
Python 依赖于协议而不是显式接口。
对象通过实现正确的方法来参与协议。
迭代协议:```python
class Count:
def __init__(self, stop):
self.current = 0
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.current >= self.stop:
raise StopIteration
value = self.current
self.current += 1
return value
```容器协议:```python
class Bag:
def __init__(self):
self.items = []
def __contains__(self, item):
return item in self.items
```上下文管理器协议:```python
class Resource:
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
```这些协议允许用户定义的对象使用以下语法:```python
for x in obj:
...
with obj:
...
if x in obj:
...
```对象模型是语法和行为之间的桥梁。
## 7.23 一个有用的内部模型
Python 表达式,例如:```python
result = obj.method(x) + y
```可以理解为对象模型操作:```text
load obj
look up attribute "method"
bind method to obj if descriptor rules apply
load x
call bound method
load y
perform binary addition through type slots
bind result name to returned object
```每一步都会移动或创建对象引用。
每个操作都由类型信息介导。
每个结果都是另一个对象引用。
## 7.24 最小 C 级草图
A simplified extension type begins with a CPython object header.```c
typedef struct {
PyObject_HEAD
long value;
} CounterObject;
```这`PyObject_HEAD`宏提供 CPython 将此内存视为 Python 对象所需的标准对象头。然后,类型对象描述了该对象的行为方式。```c
static PyTypeObject CounterType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "example.Counter",
.tp_basicsize = sizeof(CounterObject),
.tp_flags = Py_TPFLAGS_DEFAULT,
};
```真正的扩展类型需要更多的字段、初始化、方法、错误处理、模块设置和引用所有权。但形状是一样的:```text
instance memory starts with object header
type object describes behavior
runtime manipulates the object through PyObject *
```## 7.25 总结
Python 对象模型表示每个值都有标识、类型和值。 CPython 通过以下方式实现了该模型`PyObject *`指针、对象头、引用计数、类型对象和操作槽。
名称绑定到对象。容器存储对对象的引用。类型定义行为。类是一个对象。一个实例指向它的类。加法、调用、索引、迭代和属性访问等语法是通过类型导向的协议机制实现的。
该模型是 CPython 其余部分的基础:内存管理、字节码执行、属性查找、函数调用、类、描述符、扩展模块和 C API。