Skip to content

Commit 76dfdf7

Browse files
committed
新增「布尔与 None 对象」章节,并归纳全书的单例/缓存模式
1 parent 1ce15da commit 76dfdf7

7 files changed

Lines changed: 303 additions & 1 deletion

File tree

.vitepress/config.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ export default defineConfig({
5454
{ text: 'Python 列表对象', link: '/objects/list-object/' },
5555
{ text: 'Python 元组对象', link: '/objects/tuple-object/' },
5656
{ text: 'Python 字典对象', link: '/objects/dict-object/' },
57-
{ text: 'Python 集合对象', link: '/objects/set-object/' }
57+
{ text: 'Python 集合对象', link: '/objects/set-object/' },
58+
{ text: 'Python 布尔与 None 对象', link: '/objects/bool-none-object/' }
5859
]
5960
},
6061
{

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@
1818
- [Python 元组对象](objects/tuple-object/index.md)
1919
- [Python Dict 对象](objects/dict-object/index.md)
2020
- [Python Set 对象](objects/set-object/index.md)
21+
- [Python 布尔与 None 对象](objects/bool-none-object/index.md)
2122

2223
## 第 3 部分:Python 虚拟机

index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
- [x] Python 元组对象
2525
- [x] Python 字典对象
2626
- [x] Python 集合对象
27+
- [x] Python 布尔与 None 对象
2728
- [ ] Python 虚拟机
2829
- [ ] Python 编译结果
2930
- [ ] Python 虚拟机框架
Lines changed: 41 additions & 0 deletions
Loading
Lines changed: 41 additions & 0 deletions
Loading

objects/bool-none-object/index.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Python 布尔与 None 对象
2+
3+
`True``False``None` 是我们每天都在用、却很少深究的几个值。它们有个共同点:都是**单例**——全局只有一个对象。这一章我们就来看它们的实现,并顺势把贯穿全书的「用一个对象代替无数等价对象」这个省内存手法串起来。
4+
5+
```python
6+
>>> True is True, None is None
7+
(True, True)
8+
>>> True == 1, isinstance(True, int)
9+
(True, True)
10+
```
11+
12+
第二行也许让你意外:`True` 居然等于 `1`、还是 `int` 的实例?这正是布尔对象最有意思的地方。
13+
14+
## 布尔对象:是 int 的子类
15+
16+
在 CPython 里,`bool` 不是独立的类型,而是 `int`**子类**——它的类型对象 `PyBool_Type``tp_base` 指向了 `PyLong_Type`
17+
18+
`源文件:`[Objects/boolobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/boolobject.c#L134)
19+
20+
```c
21+
// Objects/boolobject.c
22+
PyTypeObject PyBool_Type = {
23+
......
24+
"bool", /* tp_name */
25+
......
26+
&PyLong_Type, /* tp_base */ // 父类是 int
27+
......
28+
bool_new, /* tp_new */
29+
};
30+
```
31+
32+
`True``False` 本身,就是两个值为 1 和 0 的**整数对象**`struct _longobject`),只不过类型被设成了 `bool`
33+
34+
`源文件:`[Objects/boolobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/boolobject.c#L177)
35+
36+
```c
37+
// Objects/boolobject.c
38+
struct _longobject _Py_FalseStruct = {
39+
PyVarObject_HEAD_INIT(&PyBool_Type, 0) // 类型 bool,值 0
40+
{ 0 }
41+
};
42+
struct _longobject _Py_TrueStruct = {
43+
PyVarObject_HEAD_INIT(&PyBool_Type, 1) // 类型 bool,值 1
44+
{ 1 }
45+
};
46+
```
47+
48+
![bool 是 int 的子类](bool-int.svg)
49+
50+
所以布尔值在数值上下文里就是 1 和 0,能直接参与运算:
51+
52+
```python
53+
>>> type(True), bool.__bases__
54+
(<class 'bool'>, (<class 'int'>,))
55+
>>> True + True, True * 3
56+
(2, 3)
57+
>>> sum([True, True, False, True]) # 统计 True 的个数,常用技巧
58+
3
59+
```
60+
61+
`sum([...])` 这个「数 `True` 的个数」是很常见的写法,背后正是因为 `True` 就是整数 1。
62+
63+
## True 与 False 是单例
64+
65+
布尔类型永远只有两个对象。看 `bool(x)` 的实现——它不新建对象,只返回 `Py_True``Py_False` 这两个现成的单例:
66+
67+
`源文件:`[Objects/boolobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/boolobject.c#L40)
68+
69+
```c
70+
// Objects/boolobject.c
71+
/* We define bool_new to always return either Py_True or Py_False */
72+
static PyObject *
73+
bool_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
74+
{
75+
......
76+
return PyBool_FromLong(ok); // 只会返回 Py_True / Py_False
77+
}
78+
```
79+
80+
```python
81+
>>> bool(5) is True, bool(0) is False
82+
(True, True)
83+
>>> bool(5) is bool(42) # 任何真值 bool() 出来都是同一个 True
84+
True
85+
```
86+
87+
正因为只有两个单例,判断真假时直接写 `if x:` 即可,不必(也不该)写 `if x == True:`
88+
89+
## None 对象
90+
91+
`None``NoneType` 类型唯一的实例,同样是一个全局单例:
92+
93+
`源文件:`[Objects/object.c](https://github.com/python/cpython/blob/v3.7.0/Objects/object.c#L1607)
94+
95+
```c
96+
// Objects/object.c
97+
PyObject _Py_NoneStruct = {
98+
_PyObject_EXTRA_INIT
99+
1, &_PyNone_Type // 引用计数 1,类型 NoneType
100+
};
101+
```
102+
103+
`Py_None` 这个宏就指向它,全局只此一个。它甚至**永远不会被销毁**——它的析构函数被设计成直接报致命错误:
104+
105+
`源文件:`[Objects/object.c](https://github.com/python/cpython/blob/v3.7.0/Objects/object.c#L1505)
106+
107+
```c
108+
// Objects/object.c
109+
static void
110+
none_dealloc(PyObject* ignore)
111+
{
112+
/* This should never get called ... */
113+
Py_FatalError("deallocating None"); // 万一 None 被销毁,直接致命错误
114+
}
115+
```
116+
117+
```python
118+
>>> None is None
119+
True
120+
>>> type(None)
121+
<class 'NoneType'>
122+
>>> type(None)() is None # 连 NoneType() 也只返回同一个 None
123+
True
124+
```
125+
126+
正因为 `None` 是单例,判断一个值是不是 `None`,应该用 **`is None`** 而不是 `== None`——`is` 直接比指针,又快又不会被自定义的 `__eq__` 干扰。
127+
128+
## 其它单例:Ellipsis 与 NotImplemented
129+
130+
除了 `None`,CPython 还有两个内建单例:
131+
132+
- **`Ellipsis`**(写作 `...`):占位用的单例,常见于 numpy 的多维切片、类型注解等。
133+
- **`NotImplemented`**:运算符方法(如 `__eq__``__add__`)在「我处理不了这种类型」时返回它,提示解释器去尝试对方的反向方法。
134+
135+
![内建单例对象](singletons.svg)
136+
137+
```python
138+
>>> ... is Ellipsis, type(...)
139+
(True, <class 'ellipsis'>)
140+
>>> NotImplemented
141+
NotImplemented
142+
```
143+
144+
> 注意 `NotImplemented`(运算符回退用的单例)和 `NotImplementedError`(一个异常类)是两回事,别混用。
145+
146+
## 单例与缓存:一个贯穿全书的模式
147+
148+
把视野拉开,你会发现 `None``True``False` 只是一个更大模式的特例——CPython 反复在用同一招:**用一个对象代替无数个等价的对象**。前面各章其实都见过它:
149+
150+
| 机制 | 谁来代替谁 | 出处 |
151+
|---|---|---|
152+
| 小整数对象池 | `[-5, 257)` 内的整数共享同一对象 | 整数对象 |
153+
| 字符串驻留 | 等值的标识符字符串共享同一对象 | 字符串对象 |
154+
| 空元组单例 | 所有 `()` 是同一个对象 | 元组对象 |
155+
| 浮点 free list | 复用最多 100 个已释放的浮点对象 | 浮点数对象 |
156+
| `None` / `True` / `False` | 各自全局唯一的单例 | 本章 |
157+
158+
![一个对象代替无数等价对象](caching-patterns.svg)
159+
160+
这套手法的共同好处有两个:**省内存**(不为等价的值重复分配),以及**比较更快**(同一对象可以直接用 `is` 比指针,而不必逐字段比较)。理解了这一点,再看 `a is b` 在不同对象上时而成立、时而不成立,就不会困惑了——成立与否,取决于该值有没有被「单例化/缓存」。
161+
162+
---
163+
164+
小结一下:
165+
166+
- `bool``int`**子类**`True`/`False` 就是值为 1/0 的整数对象(类型为 `bool`),所以能参与数值运算(`sum(bools)` 数个数);
167+
- `True`/`False`/`None` 都是**单例**`bool(x)``NoneType()` 都只返回现成的单例对象,判断时用 `if x:``is None`
168+
- `None` 永不销毁(`none_dealloc` 会触发致命错误);另有 `Ellipsis``NotImplemented` 两个内建单例;
169+
- 这些单例是 CPython「**用一个对象代替无数等价对象**」这一省内存模式的体现,和小整数池、字符串驻留、空元组、浮点 free list 一脉相承——既省内存,又让 `is` 比较更快。
Lines changed: 48 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)