最近因为学校大作业的缘故,研究了一下Python2虚拟机的工作原理。在此过程中,Python2中四类变量的区别和联系一直搞得我有点头晕,故在此记录,以供日后备查。
实验环境:Python 2.7.18
Names(全局变量及属性)
names列表包含了函数使用的所有全局变量名和对其它模块成员的引用名称(也就是非本地但在函数中使用的变量),以及访问属性时使用的名字。比如,当你在函数中引用另一个模块中定义的变量或函数,或者对对象进行属性访问时,这些名字就会被存储在names列表中。简而言之,当Python代码尝试访问一个不是明确声明为局部变量的变量时,它首先尝试在names中找到它。
example1:
s='HelloWorld'
s.upper()
print(s)
print(len(s))
编译器生成的字节码节选如下:
<dis>
1 0 LOAD_CONST 0 ('HelloWorld')
3 STORE_NAME 0 (s)
2 6 LOAD_NAME 0 (s)
9 LOAD_ATTR 1 (upper)
12 CALL_FUNCTION 0
15 POP_TOP
3 16 LOAD_NAME 0 (s)
19 PRINT_ITEM
20 PRINT_NEWLINE
4 21 LOAD_NAME 2 (len)
24 LOAD_NAME 0 (s)
27 CALL_FUNCTION 1
30 PRINT_ITEM
31 PRINT_NEWLINE
32 LOAD_CONST 1 (None)
35 RETURN_VALUE
</dis>
<names> ('s', 'upper', 'len')</names>
<varnames> ()</varnames>
<freevars> ()</freevars>
<cellvars> ()</cellvars>
<consts>
'HelloWorld'
None
</consts>
VarNames(局部变量)
varnames列表包含了函数的局部变量名称。这些是在函数定义内部声明的变量,包括函数的参数。在函数体内定义的变量,Python解释器会将其视为局部变量,并将其名称加入到varnames列表中。函数内部的变量名解析首先会在varnames中进行。
example1:
def add(a, b):
result = a + b
return result
编译器生成的字节码节选如下:
<dis>
11 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 BINARY_ADD
7 STORE_FAST 2 (result)
12 10 LOAD_FAST 2 (result)
13 RETURN_VALUE
</dis>
<names> ()</names>
<varnames> ('a', 'b', 'result')</varnames>
<freevars> ()</freevars>
<cellvars> ()</cellvars>
example2
import math
def example_func():
# 引用全局变量
global_var = math.pi
# 访问一个对象的属性
result = math.sqrt(4)
return result
编译器生成的字节码节选如下:
<dis>
5 0 LOAD_GLOBAL 0 (math)
3 LOAD_ATTR 1 (pi)
6 STORE_FAST 0 (global_var)
7 9 LOAD_GLOBAL 0 (math)
12 LOAD_ATTR 2 (sqrt)
15 LOAD_CONST 1 (4)
18 CALL_FUNCTION 1
21 STORE_FAST 1 (result)
8 24 LOAD_FAST 1 (result)
27 RETURN_VALUE
</dis>
<names> ('math', 'pi', 'sqrt')</names>
<varnames> ('global_var', 'result')</varnames>
<freevars> ()</freevars>
<cellvars> ()</cellvars>
我们注意到函数example_func内部的局部变量global_var、result都被解析为了局部变量(varname),而引用自外部库的变量名和方法名math、pi、sqrt都被解析成了全局变量(name)。
FreeVars (自由变量)
freevars包含了所谓的"自由变量"的名称,这些变量既不是全局的,也不是局部的,它们来源于包裹这个函数的外部作用域(但不是全局作用域)。在嵌套函数中,如果内部函数引用了外部函数的局部变量,那么这个局部变量对于内部函数而言,就是自由变量。 它们在闭包(closure)的上下文中特别重要。
example
def outer_func(x):
def inner_func(y):
return x + y
return inner_func
# 使用outer_func定义函数
my_func = outer_func(10)
# 调用内部函数
print(my_func(5)) # 输出 15
编译器为内部函数inner_func生成的字节码节选如下:
<dis>
3 0 LOAD_DEREF 0 (x)
3 LOAD_FAST 0 (y)
6 BINARY_ADD
7 RETURN_VALUE
</dis>
<names> ()</names>
<varnames> ('y',)</varnames>
<freevars> ('x',)</freevars>
<cellvars> ()</cellvars>
<name> 'inner_func'</name>
在这个例子中,inner_func是一个内部函数,引用了它的外部函数outer_func的参数x。因此,对于inner_func来说,x是一个自由变量,它会出现在inner_func的freevars列表中。
CellVar(单元变量)
cellvars列表包含了那些被嵌套在内部函数中访问的本地变量的名称。这些变量实际上是位于外围函数的局部作用域内,但当它们被内部函数引用时,它们需要以特殊的方式处理以维持它们的状态。这主要涉及闭包的实现,当外部函数的局部变量被内部函数引用时,这些变量就会被存放在cellvars中。
example1
使用和freevars相似的例子,但是这次我们关注的是外部函数的角度:
def outer_func(x):
def inner_func(y):
return x + y
return inner_func
编译器为外部函数outer_func生成的字节码如下:
<dis>
2 0 LOAD_CLOSURE 0 (x)
3 BUILD_TUPLE 1
6 LOAD_CONST 1 (<code object inner_func at 00000000033F0CB0, file "C:\Users\Administrator\Desktop\test.py", line 2>)
9 MAKE_CLOSURE 0
12 STORE_FAST 1 (inner_func)
4 15 LOAD_FAST 1 (inner_func)
18 RETURN_VALUE
</dis>
<names> ()</names>
<varnames> ('x', 'inner_func')</varnames>
<freevars> ()</freevars>
<cellvars> ('x',)</cellvars>
<name> 'outer_func'</name>
在这个例子中,从outer_func的角度来看,x不仅仅是一个局部变量,因为它被嵌套的inner_func所引用,这样的变量需要特殊的处理以供内部函数访问,而且它在创建闭包时起到了“桥梁”的作用。因此,x会出现在outer_func编译结果的cellvars列表中。