曾经被问到 “Python里 a, b = b, a” 是怎么实现的?
准备工作
在回答这个问题之前,先介绍一个库 - dis。使用这个库,可以更清楚地看到Python是如何实现数值交换的。
dis.dis([bytesource])
Disassemble the bytesource object.
bytesource can denote either a module, a class, a method, a function, or a code object.
For a module, it disassembles all functions.
For a class, it disassembles all methods.
For a single code sequence, it prints one line per bytecode instruction.
If no object is provided, it disassembles the last traceback.
也就是对一个模块、类、方法、函数、代码对象进行反编译。
a, b = b, a
>>> def f(a, b):
... a, b = b, a
...
>>> import dis
>>> dis.dis(f)
2 0 LOAD_FAST 1 (b)
3 LOAD_FAST 0 (a)
6 ROT_TWO
7 STORE_FAST 0 (a)
10 STORE_FAST 1 (b)
13 LOAD_CONST 0 (None)
16 RETURN_VALUE
- LOAD_FAST,把右边的b入栈
- LOAD_FAST,把右边的a入栈
- ROT_TWO,把栈顶的两个元素交换,即b在栈顶,a在第二个位置
- STORE_FAST,把栈顶元素赋值给a(即把b给a)
- STORE_FAST,把栈顶元素赋值给b(即把a给b)
至此,实现了数值交换。
a, b, c = b, c, a
上面介绍的是两个数值的交换,那么三个数值是如何交换的呢?
>>> def f(a, b, c):
... a, b, c = b, c, a
...
>>> dis.dis(f)
2 0 LOAD_FAST 1 (b)
3 LOAD_FAST 2 (c)
6 LOAD_FAST 0 (a)
9 ROT_THREE
10 ROT_TWO
11 STORE_FAST 0 (a)
14 STORE_FAST 1 (b)
17 STORE_FAST 2 (c)
20 LOAD_CONST 0 (None)
23 RETURN_VALUE
这里多了一个 ROT_THREE,它的意思是把栈顶元素放到第三个位置,然后原先的第二、第三位置的元素上移。
栈的情况如下:
栈顶 | ||
---|---|---|
b | c | a |
ROT_THREE之后:
栈顶 | ||
---|---|---|
a | b | c |
ROT_TWO之后:
栈顶 | ||
---|---|---|
a | c | b |
然后出栈、赋值即可实现数值交换。
那么还有没有 ROT_FOUR 和 ROT_FIVE 呢?
答案是没有了!更多的数值交换下面即将介绍。
更多变量的数值交换,以及给多变量赋值
>>> def f(a,b,c,d):
... a,b,c,d = b,c,d,a
...
>>> dis.dis(f)
2 0 LOAD_FAST 1 (b)
3 LOAD_FAST 2 (c)
6 LOAD_FAST 3 (d)
9 LOAD_FAST 0 (a)
12 BUILD_TUPLE 4
15 UNPACK_SEQUENCE 4
18 STORE_FAST 0 (a)
21 STORE_FAST 1 (b)
24 STORE_FAST 2 (c)
27 STORE_FAST 3 (d)
30 LOAD_CONST 0 (None)
33 RETURN_VALUE
>>> def f(a, b, c, d):
... a, b, c, d = [1,2,3,4]
...
>>> dis.dis(f)
2 0 LOAD_CONST 1 (1)
3 LOAD_CONST 2 (2)
6 LOAD_CONST 3 (3)
9 LOAD_CONST 4 (4)
12 BUILD_LIST 4
15 UNPACK_SEQUENCE 4
18 STORE_FAST 0 (a)
21 STORE_FAST 1 (b)
24 STORE_FAST 2 (c)
27 STORE_FAST 3 (d)
30 LOAD_CONST 0 (None)
33 RETURN_VALUE
>>> def f(a,b,c,d):
... a,b,c,d = (1,2,3,4)
...
>>> dis.dis(f)
2 0 LOAD_CONST 5 ((1, 2, 3, 4))
3 UNPACK_SEQUENCE 4
6 STORE_FAST 0 (a)
9 STORE_FAST 1 (b)
12 STORE_FAST 2 (c)
15 STORE_FAST 3 (d)
18 LOAD_CONST 0 (None)
21 RETURN_VALUE
>>> def f(a,b,c,d):
... a,b,c,d = 1,2,3,4
...
>>> dis.dis(f)
2 0 LOAD_CONST 5 ((1, 2, 3, 4))
3 UNPACK_SEQUENCE 4
6 STORE_FAST 0 (a)
9 STORE_FAST 1 (b)
12 STORE_FAST 2 (c)
15 STORE_FAST 3 (d)
18 LOAD_CONST 0 (None)
21 RETURN_VALUE
其中UNPACK_SEQUENCE
的意思是把栈顶的多个元素打开成独立的值,从右向左摆放(也就是 reverse 一下)
Unpacks TOS into count individual values, which are put onto the stack right-to-left.
TOS => top of stack
总结
- Python为了实现数值交换的特性,特地实现了两个方法:ROT_TWO和ROT_THREE。
- dis这个库可以很好地帮助我们去看清Python的内部实现,从而能够更深入地了解Pythonic,以及用更加Pythonic的方式去编程。
网友评论