python学习笔记 - local, global and free variable

理解 global and local variable

现在我想在子代码块中对global variable重新赋值,实现代码如下:

code snippet 1

>>>
>>> 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': , '__builtins__': , '__package__': None, 'var': 'outer val', '__name__': '__main__', '__doc__': None}
>>>
>>> print "var: ", var
var:  outer val
>>>

但结果并非如我所愿。在func1内部,var的值为"inner val",离开了函数,其值又变成了"outer val",为什么?看看输出的本地和全局变量列表答案就明显了,var既出现在本地变量列表,又出现在全局变量列表,但绑定的值不一样,因为他们本就是两个不同的变量。也就是说,当你想在子代码块中对某个全局变量重新赋值时,实际上你只是定义了一个同名的本地变量,这并不会改变全局变量的值。

如何修改全局变量的值?需要在函数中使用global statement。先看一例:

code snippet 2

>>> def func2():
...     global var2
...     var2 = 'who am i'
...     print "locals(): ", locals()
...     print "globals(): ", globals()
...
>>> func2()
locals():  {}
globals():  {'func2': , 'func1': , 'var2': 'who am i', '__builtins__': , '__package__': None, '__name__': '__main__', '__doc__': None}
>>>
>>> print var2
who am i
>>>

看看输出结果,发现竟然可以在函数代码块中定义一个全局变量,太神奇了!因为全局变量的作用域是整个模块,所以离开了定义函数,依然可以读取它。

有了这个做铺垫,再来尝试修改全局变量的值:

code snippet 3

>>> global_var = 'outer'
>>> def func3():
...     global global_var
...     global_var = 'inner'
...     print "locals(): ", locals()
...     print "globals(): ", globals()
...
>>> func3()
locals():  {}
globals():  {'func3': , '__builtins__': , '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”,我是这样理解的,对于模块代码块来说,模块级的变量就是它的本地变量,但对于模块中其他更小的代码块来说,这些变量就是全局的。那再进一步,什么是模块级的变量,是指那些在模块内部,但在其所有子代码块之外定义的变量吗?用代码验证哈:

code snippet 4

>>>
>>> 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': , 'local_var': 'local_val'}
showval.globals(): {'__builtins__': , 'global_var': 'global_val', '__package__': None, '__name__': '__main__', '__doc__': None, 'showval': }
>>>
>>> a_func

>>> 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.

好像我已经把自由变量搞得很清楚了,但是真的吗?看看下面的代码:

code snippet 5

>>>
>>> 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 "", line 1, in 
  File "", 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__': , 'global_var': 'foo', '__package__': None, 'ex2': , 'ex1': , '__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,那修改自由变量的值又会如何?先用自然思维方式试试:

code snippet 6

>>>
>>> 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': }
>>>

在innerfunc函数中我试图修改自由变量var的值,结果却发现修改只在innerfunc函数内有效,离开此函数后其值仍然是"free"。看看输出的outerfunc和innerfunc函数的本地变量列表就知道咋回事儿了。当你想在闭包函数中对自由变量重新赋值时,实际上你只是在这里定义了一个本地变量,这并不会改变自由变量的值。

那怎样才能修改自由变量的值?在 Notes on Python variable scope 中找到了两种不错的方式:

code snippet 7

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

code snippet 8

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

参考资源:

  1. python executin model
  2. Notes on Python variable scope
  3. Python Closures and Free Variables
  4. python模块的内置方法

你可能感兴趣的:(python学习笔记 - local, global and free variable)