代码A:
1 x=12
2 def foo():
3 #print x
4 x=x+1
5 print x
6
7 foo()
执行上面的代码会输出什么呢?本人写python代码是还真心没注意过,一般也是把x作为
foo()的参数。如果你执行上面的代码会报错:
UnboundLocalError: local variable 'x' referenced before assignment
不明白的话,看起来确实如此诡异!c语言可不会出现这种情况。如果没有第4行就会正常输出12。
所以问题也就在第4行上。根据报错,大体原因应该知道了,那么我们改写一下上面的代码,代码B:
1 x=12
2 print hex(id(x))
3 def foo():
4 x=1
5 print hex(id(x))
6 print x
7 foo()
本人运行输出的是:
0x1d5d6a4
12
0x1d5d728
看以看到id值并不相等,也就是foo函数的x是一个全新的变量,而且作用域仅在函数内。
代码A中x=x+1并不是我们想当然等号右边的x是函数外部的x,这样也不是容易看明白的,
我们使用dis模块反汇编一下。
代码C:
1 import dis
2 x=12
3 def foo():
4 y=x+1
5 print y
6
7 dis.dis(foo)
运行输出:
4 0 LOAD_GLOBAL 0 (x)
3 LOAD_CONST 1 (1)
6 BINARY_ADD
7 STORE_FAST 0 (y)
5 10 LOAD_FAST 0 (y)
13 PRINT_ITEM
14 PRINT_NEWLINE
15 LOAD_CONST 0 (None)
18 RETURN_VALUE
代码D:
1 import dis
2 x=12
3 def foo():
4 x=x+1
5 print x
6
7 dis.dis(foo)
运行输出:
4 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (1)
6 BINARY_ADD
7 STORE_FAST 0 (x)
5 10 LOAD_FAST 0 (x)
13 PRINT_ITEM
14 PRINT_NEWLINE
15 LOAD_CONST 0 (None)
18 RETURN_VALUE
比较一下代码D与代码C的不同,然后再看两段代码的输出。这里说一下,输出的第一列是代码的行号,
第二列是指令在字节码的偏移,第三列指令,第四列示栈的索引序号。
OK,我们看两段代码输出的第一行红色标记的指令,代码C是LOAD_GLOBAL指令,而代码D是LOAD_FAST。
根据手册我们知道:
LOAD_GLOBAL :载入全局co_names[namei]的值到栈上,namei就是此指令后面跟随的值(也就是输出的第四列);
LOAD_FAST :压入本地co_varnames[var_num]到栈上,var_num是此指令后面跟随的值。
所以代码C是用全局的x加1,而代码D是利用本地的x加1,但是本地的x没有对应的索引值,必然就错了。
一个变量在另一个作用域中不能做自加等类似的运算,对一个变量赋值相当于c语言中变量的声明并初始化。
上面真正的字节码可以通过foo.func_code.co_code.encode('hex')获得,如代码D的输出:
7c0000640100177d00007c0000474864000053
7c指LOAD_FAST等等...
再来看另一个例子:
1 x=2
2 print hex(id(x))
3 def foo():
4 x=2
5 print hex(id(x))
6 foo()
输出:
0x9551314
0x9551314
可以看出函数外的2与函数内的2是同一个。
从索引数我们也能看出来:
1 x=123456789
2 #print hex(id(x))
3 print sys.getrefcount(x),
4 def foo():
5 x=123456789
6 #print hex(id(x))
7 print sys.getrefcount(x),
8
9 foo()
10 print sys.getrefcount(x)
输出:3 4 3
在foo()内时增加了一为4,函数结束又变成了3.