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

理解 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

参考资源:

  1. python executin model
  2. Notes on Python variable scope
  3. Python Closures and Free Variables
  4. python模块的内置方法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容