理解 global and local variable
现在我想在子代码块中对global variable重新赋值,实现代码如下:
<a id="code-snippet-1">code snippet 1</a>
>>>
>>> var = 'outer val'
>>> def func1():
... var = 'inner val'
... print "Inside func1(), val: ", var
... print "locals(): ", locals()
... print "globals(): ", globals()
...
>>> func1()
Inside func1(), val: inner val
locals(): {'var': 'inner val'}
globals(): {'func1': <function func1 at 0x10a20b938>, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'var': 'outer val', '__name__': '__main__', '__doc__': None}
>>>
>>> print "var: ", var
var: outer val
>>>
但结果并非如我所愿。在func1内部,var的值为"inner val",离开了函数,其值又变成了"outer val",为什么?看看输出的本地和全局变量列表答案就明显了,var既出现在本地变量列表,又出现在全局变量列表,但绑定的值不一样,因为他们本就是两个不同的变量。也就是说,当你想在子代码块中对某个全局变量重新赋值时,实际上你只是定义了一个同名的本地变量,这并不会改变全局变量的值。
如何修改全局变量的值?需要在函数中使用global statement。先看一例:
<a id="code-snippet-2">code snippet 2</a>
>>> def func2():
... global var2
... var2 = 'who am i'
... print "locals(): ", locals()
... print "globals(): ", globals()
...
>>> func2()
locals(): {}
globals(): {'func2': <function func2 at 0x10a20bd70>, 'func1': <function func1 at 0x10a20b938>, 'var2': 'who am i', '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, '__name__': '__main__', '__doc__': None}
>>>
>>> print var2
who am i
>>>
看看输出结果,发现竟然可以在函数代码块中定义一个全局变量,太神奇了!因为全局变量的作用域是整个模块,所以离开了定义函数,依然可以读取它。
有了这个做铺垫,再来尝试修改全局变量的值:
<a id="code-snippet-3">code snippet 3</a>
>>> global_var = 'outer'
>>> def func3():
... global global_var
... global_var = 'inner'
... print "locals(): ", locals()
... print "globals(): ", globals()
...
>>> func3()
locals(): {}
globals(): {'func3': <function func3 at 0x109dce938>, '__builtins__': <module '__builtin__' (built-in)>, 'global_var': 'inner', '__package__': None, '__name__': '__main__', '__doc__': None}
>>>
>>> print global_var
inner
>>>
妥妥的了。做个总结吧,要在函数代码块中修改全局变量的值,就使用global statement声明一个同名的全局变量,然后就可以修改其值了;如果事先不存在同名全局变量,此语句就会定义一个,即使离开了当前函数也可以使用它。
理解 global and free variable
来自于python官方文档 Execution Model 的解释:
When a name is used in a code block, it is resolved using the nearest enclosing scope. The set of all such scopes visible to a code block is called the block’s environment.
If a name is bound in a block, it is a local variable of that block. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.
这段文档确实应该多读多想。全局变量不用多说,一看就懂。不过这句话有点让人费解,“The variables of the module code block are local and global”,我是这样理解的,对于模块代码块来说,模块级的变量就是它的本地变量,但对于模块中其他更小的代码块来说,这些变量就是全局的。那再进一步,什么是模块级的变量,是指那些在模块内部,但在其所有子代码块之外定义的变量吗?用代码验证哈:
<a id="code-snippet-4">code snippet 4</a>
>>>
>>> global_var = 'global_val'
>>> def showval():
... local_var = 'local_val'
... print "before defining inner func, showval.locals(): ", locals()
... def innerFunc():
... print "Inside innerFunc, local_var: ", local_var
... print "innerFunc.locals:", locals()
... print "after defining inner func, showval.locals(): ", locals()
... print "showval.globals():", globals()
... return innerFunc
...
>>> a_func = showval()
before defining inner func, showval.locals(): {'local_var': 'local_val'}
after defining inner func, showval.locals(): {'innerFunc': <function innerFunc at 0x109caed70>, 'local_var': 'local_val'}
showval.globals(): {'__builtins__': <module '__builtin__' (built-in)>, 'global_var': 'global_val', '__package__': None, '__name__': '__main__', '__doc__': None, 'showval': <function showval at 0x109cae938>}
>>>
>>> a_func
<function innerFunc at 0x109caed70>
>>> a_func()
Inside innerFunc, local_var: local_val
innerFunc.locals: {'local_var': 'local_val'}
>>>
基于自己的理解来分析哈输出结果:变量local_var、函数innerFunc是在函数代码块showval中定义的,所以它们是该代码块的局部变量。首次输出globals时,变量global_var、函数showval在所有子代码块之外定义,所以它们是当前模块的全局变量。再次输出globals时,多了一个全局变量globals。
注意看,全局变量中还有:__builtins__、__package__、__name__、__doc__。这些变量的含义可参考 python模块的内置方法。
现在来看自由变量,local_var被innerFunc函数使用,但是并未在其中定义,所以对于innerFunc代码块来说,它就是自由变量。问题来了,自由变量为什么出现在
innerFunc.locals()的输出结果中?这就需要看看locals()API文档了:
Update and return a dictionary representing the current local symbol table. Free variables are returned by locals() when it is called in function blocks, but not in class blocks.
好像我已经把自由变量搞得很清楚了,但是真的吗?看看下面的代码:
<a id="code-snippet-5">code snippet 5</a>
>>>
>>> global_var = 'foo'
>>> def ex1():
... local_var = 'bar'
... print "locals(): ", locals()
... print global_var
... print local_var
... global_var = 'foo2'
...
>>> ex1()
locals(): {'local_var': 'bar'}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in ex1
UnboundLocalError: local variable 'global_var' referenced before assignment
>>>
>>> def ex2():
... local_var = 'bar'
... print "locals(): ", locals()
... print "globals(): ", globals()
... print global_var
... print local_var
...
>>> ex2()
locals(): {'local_var': 'bar'}
globals(): {'__builtins__': <module '__builtin__' (built-in)>, 'global_var': 'foo', '__package__': None, 'ex2': <function ex2 at 0x10b91aed8>, 'ex1': <function ex1 at 0x10b91aaa0>, '__name__': '__main__', '__doc__': None}
foo
bar
>>>
首先分析哈输出结果。执行ex1()函数失败,原因:UnboundLocalError: local variable 'global_var' referenced before assignment,意思是global_var这个本地变量还未赋值就被引用了。等等,global_var怎么是本地变量了?与ex2()函数做个对比,发现因为有这行代码global_var = 'foo2',解释器就认为global_var是一个本地变量,而不是全局变量。
那最外面定义的global_var到底是本地还是全局变量?看看第二部分的输出,可以很确定地知道最外面定义的global_var是一个全局变量。
简单点说,就是这里存在两个同名的变量,一个是最外面定义的全局变量global_var,另一个是函数ex1()中定义的本地变量global_var。
再等等,矛盾出现了。按照之前对自由变量的理解,在一个代码块中被使用,但是并未在那儿定义,外部定义的global_var在函数块ex1()中被使用,完全符合,它为什么不是一个自由变量?还是我们对自由变量的理解有问题?
与code snippet 4中确定是自由变量的local_var比较,发现local_var是一个函数代码块的本地变量,而code snippet 5中的global_var是一个模块代码块的本地变量。当local_var被定义它的函数内的嵌套函数使用时,它就变成了一个自由变量,此时函数的嵌套就形成了闭包(closure),可见自由变量的应用场景就是闭包。
而global_var对模块本身来说它是一个本地变量,但在模块中的其他代码块中也可以使用它,所以它同时又是一个全局变量。
到这儿就可以对自由变量的理解做个总结了:如果一个变量在函数代码块中定义,但在其他代码块中被使用,例如嵌套在外部函数中的闭包函数,那么它就是自由变量。注意,给变量赋值不算是使用该变量,使用指的是读取变量值,具体区别后面再详述。
理解 free and local variable
修改全局变量的值需要使用global,那修改自由变量的值又会如何?先用自然思维方式试试:
<a id="code-snippet-6">code snippet 6</a>
>>>
>>> def outerfunc():
... var = 'free'
... def innerfunc():
... var = 'inner'
... print "Inside innerfunc, var: ", var
... print "Inside innerfunc, locals(): ", locals()
... innerfunc()
... print "var: ", var
... print "locals(): ", locals()
...
>>> outerfunc()
Inside innerfunc, var: inner
Inside innerfunc, locals(): {'var': 'inner'}
var: free
locals(): {'var': 'free', 'innerfunc': <function innerfunc at 0x102cf0410>}
>>>
在innerfunc函数中我试图修改自由变量var的值,结果却发现修改只在innerfunc函数内有效,离开此函数后其值仍然是"free"。看看输出的outerfunc和innerfunc函数的本地变量列表就知道咋回事儿了。当你想在闭包函数中对自由变量重新赋值时,实际上你只是在这里定义了一个本地变量,这并不会改变自由变量的值。
那怎样才能修改自由变量的值?在 Notes on Python variable scope 中找到了两种不错的方式:
<a id="code-snippet-7">code snippet 7</a>
class Namespace: pass
def ex7():
ns = Namespace()
ns.var = 'foo'
def inner():
ns.var = 'bar'
print 'inside inner, ns.var is ', ns.var
inner()
print 'inside outer function, ns.var is ', ns.var
ex7()
# console output
inside inner, ns.var is bar
inside outer function, ns.var is bar
<a id="code-snippet-8">code snippet 8</a>
def ex8():
ex8.var = 'foo'
def inner():
ex8.var = 'bar'
print 'inside inner, ex8.var is ', ex8.var
inner()
print 'inside outer function, ex8.var is ', ex8.var
ex8()
# console output
inside inner, ex8.var is bar
inside outer function, ex8.var is bar