Skip to content

Commit b81b193

Browse files
committed
Aug-05 post
1 parent e9bbfcd commit b81b193

File tree

8 files changed

+462
-6
lines changed

8 files changed

+462
-6
lines changed

content/posts/python-tricks.md

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
+++
2+
date = '2025-08-05T10:30:00+08:00'
3+
draft = false
4+
title = 'Python Tricks'
5+
+++
6+
7+
### 1. The Self-Replicating Trick
8+
将一个含有空列表的列表乘5, 得到有5个空列表的列表
9+
```python
10+
x = [[]] * 5
11+
x
12+
```
13+
> [[], [], [], [], []]
14+
15+
当使用`.append("x")`方法时, 所有列表都被修改
16+
```python
17+
x[0].append("x")
18+
x
19+
```
20+
> [["x"], ["x"], ["x"], ["x"], ["x"]]
21+
22+
打印其 id 可以看到, id 都相同
23+
```python
24+
for item in x:
25+
print(id(item))
26+
```
27+
> 4417579584
28+
> 4417579584
29+
> 4417579584
30+
> 4417579584
31+
> 4417579584
32+
33+
或者使用`set()`发现 id 唯一
34+
```python
35+
set(id(item) for item in x)
36+
```
37+
> {4417579584}
38+
39+
也就是说, 当使用乘法的时候, 创建了5个内部列表的引用副本
40+
41+
使用反汇编发现, 只创建了两个列表, 并执行乘5
42+
```python
43+
dis.dis("[[]] * 5")
44+
45+
0 0 RESUME 0 # 用于支持解释器恢复 (py3.11)
46+
47+
1 2 BUILD_LIST 0 # 构造一个空列表[], 压栈
48+
4 BUILD_LIST 1 # 从栈顶取一个对象, 构造列表[[]]
49+
6 LOAD_CONST 0 (5) # 加载常量 5
50+
8 BINARY_OP 5 (*) # 对栈顶两个元素执行乘法
51+
12 RETURN_VALUE # 返回栈顶结果
52+
```
53+
54+
#### The alternative
55+
如果要构造独立列表, 应改用列表推导式
56+
57+
```python
58+
x = [[] for _ in range(5)]
59+
x
60+
```
61+
> [[], [], [], [], []]
62+
63+
```python
64+
for item in x:
65+
print(id(item))
66+
```
67+
> 4587832384
68+
> 4587818752
69+
> 4587831168
70+
> 4587839168
71+
> 4587809152
72+
73+
```python
74+
set(id(item) for item in x)
75+
```
76+
> {4587809152, 4587818752, 4587831168, 4587832384, 4587839168}
77+
78+
79+
### 2. The Teleportation Trick
80+
```python
81+
def add_to_shopping_list(item, shopping_list=[]):
82+
shopping_list.append(item)
83+
return shopping_list
84+
```
85+
上面函数为一个空列表中添加一个 item, 期望每次创建一个新的空列表, 并添加一个item
86+
87+
```python
88+
groceries = add_to_shopping_list("Bread")
89+
groceries
90+
```
91+
> ['Bread']
92+
93+
94+
```python
95+
books = add_to_shopping_list("A Brief History of Time")
96+
books
97+
```
98+
> ['Bread', 'A Brief History of Time']
99+
100+
然而, 'Bread' 被传送到 books 里面去了
101+
102+
下面不使用默认参数, 测试一下函数
103+
```python
104+
cakes = []
105+
cakes = add_to_shopping_list("Chorolate Cake", cakes)
106+
cakes
107+
```
108+
> ['Chorolate Cake']
109+
110+
```python
111+
tools = []
112+
tools = add_to_shopping_list("Snapper", tools)
113+
tools
114+
```
115+
> ['Snapper']
116+
117+
当传入一个存在的列表时, 没有发生传送行为
118+
119+
回到函数定义: 默认参数的列表, 在函数定义的时候已经被创建了, 因此每次使用该函数而不传入列表参数的时候, 默认列表`shopping_list`就会被使用, 且 list 是一个可变类型, 因此每次会修改这个列表
120+
121+
使用下面方法, 每次打印出使用列表的 id, 会发现不传入列表参数时的 id 都相同
122+
```python
123+
def add_to_shopping_list(item, shopping_list=[]):
124+
print(id(shopping_list))
125+
shopping_list.append(item)
126+
return shopping_list
127+
```
128+
129+
#### The alternative
130+
这个 bug 在使用可变类型(mutable)作为默认参数的时候都会发生, 应该避免可变数据类型作为默认参数
131+
132+
如果想要默认值参数, 可以考虑使用 None 作为参数默认值
133+
```python
134+
def add_to_shopping_list(item, shopping_list=None):
135+
if shopping_list is None:
136+
shopping_list = []
137+
shopping_list.append(item)
138+
return shopping_list
139+
```
140+
141+
142+
### The Vanishing Trick
143+
下面是最后一个 trick
144+
```python
145+
doubles = (number * 2 for number in range(10))
146+
147+
4 in doubles
148+
# True
149+
150+
4 in doubles
151+
# False
152+
```
153+
上面结果看起来很矛盾, 4 怎么一会儿在 doubles 中, 一会儿又不再 doubles 中?
154+
155+
再来看一个例子
156+
```python
157+
another_doubles = [number * 2 for number in range(10)]
158+
159+
4 in another_doubles
160+
# True
161+
162+
4 in another_doubles
163+
# True
164+
```
165+
上面这个例子中, 就都是 True
166+
167+
问题出在, 当使用括号()创建 doubles 的时候, 并不是创建了元组 tuple, 而是一个生成器 generator
168+
```python
169+
doubles = (number ** 2 for number in range(10))
170+
doubles
171+
```
172+
> <generator object <genexpr> at 0x111718e10>
173+
174+
生成器并不会包含所有的值, 而是在使用的时候生成每个值
175+
例如, 调用 next() 会返回下一个值
176+
```python
177+
next(doubles)
178+
# 0
179+
180+
next(doubles)
181+
# 1
182+
183+
next(doubles)
184+
# 2
185+
186+
next(doubles)
187+
# 4
188+
189+
...
190+
191+
next(doubles)
192+
# 18
193+
194+
next(doubles)
195+
```
196+
> ---------------------------------------------------------------------------
197+
> StopIteration Traceback (most recent call last)
198+
199+
生成器是一次型的数据结构, 当生成下一个数据的时候, 之前的数据不会被保存, 也就是只能遍历数据一次
200+
201+
一但遍历完成, 就会报`StopIteration`的错误, 所以当运行`4 in doubles`的时候, 先得到0, 为 False, 生成器会继续遍历下一个, 直到得到4, 当再次调用的时候, 下一个返回6, 直到遍历结束也无法得到到4
202+
203+
同样的行为在迭代器 iterator 上也一样
204+
```python
205+
numbers = [2, 4, 6, 8]
206+
numbers_rev = reversed(numbers)
207+
numbers_rev
208+
# <list_reverseiterator object at 0x......>
209+
210+
4 in numbers_rev
211+
# True
212+
213+
4 in numbers_rev
214+
# False
215+
```
216+
217+
#### The alternative
218+
如果使用生成器, 要知道只能遍历每个元素一次, 如果要获得一个有所有元素的数据结构, 应该使用元素 tuple 或列表 list
219+
```python
220+
doubles = (number * 2 for number in range(10)) # generator
221+
more_doubles = tuple(number * 2 for number in range(10)) # tuple
222+
223+
4 in more_doubles
224+
# True
225+
226+
4 in more_doubles
227+
# True
228+
```

0 commit comments

Comments
 (0)