[妖路-1] 名称空间,作用域,生存期

#!/usr/bin/env python
# encoding: utf-8
def func1():
    x = 1
    print globals()
    print 'before func1:', locals()
    def func2():
        a = 1
        print 'before fun2:', locals()
        a += x
        print 'after fun2:', locals()
    func2()
    print 'after func1:', locals()
    print globals()
if __name__ == '__main__':
    func1()

###
{'func1': <function func1 at 0x02BFF6B0>, '__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None}
before func1: {'x': 1}
before fun2: {'a': 1, 'x': 1}
after fun2: {'a': 2, 'x': 1}
after func1: {'x': 1, 'func2': <function func2 at 0x02BFF6F0>}
{'func1': <function func1 at 0x02BFF6B0>, '__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None}
#!/usr/bin/env python
# encoding: utf-8
def func1():
    x = 1
    print 'before func1:', locals()
    def func2():
        print 'before fun2:', locals()
        x += x #就是这里使用x其余地方不变
        print 'after fun2:', locals()
    func2()
    print 'after func1:', locals()
if __name__ == '__main__':
    func1()

###
{'func1': <function func1 at 0x02BAF6B0>, '__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None}
before func1: {'x': 1}
before fun2: {'a': 1}

Traceback (most recent call last):
  File "C:\Desktop\b.py", line 21, in <module>
    func1()
  File "C:\Desktop\b.py", line 16, in func1
    func2()
  File "C:\Desktop\b.py", line 13, in func2
    x += x
UnboundLocalError: local variable 'x' referenced before assignment

python里面有很多名字空间,每个地方都有自己的名字空间,互不干扰,不同空间中的两个相同名字的变量之间没有任何联系一般有4种: LEGB四种
- locals: 函数内部的名字空间,一般包括函数的局部变量以及形式参数
- enclosing function: 在嵌套函数中外部函数的名字空间, 对fun2来说, fun1的名字空间就是。
- globals: 当前的模块空间,模块就是一些py文件。也就是说,globals()类似全局变量。
- builtins: 内置模块空间, 也就是内置变量或者内置函数的名字空间。

当程序引用某个变量的名字时,就会从当前名字空间开始搜索。搜索顺序规则便是: LEGB.
一层一层的查找,找到了之后,便停止搜索,如果最后没有找到,则抛出在NameError的异常。这里暂时先不讨论赋值操作。 比如例1中的a = x + 1 这行代码,需要引用x, 则按照LEGB的顺序查找,locals()也就是func2的名字空间没有,进而开始E,也就是func1,里面有,找到了,停止搜索,还有后续工作,就是把x也加到自己的名字空间,这也是为什么fun2的名字空间里面也有x的原因。

from A import B和import A的一点区别

#!/usr/bin/env python
# encoding: utf-8
import copy
from copy import deepcopy
def func():
    x = 123
    print 'func locals:',locals()
s = 'hello world'
if __name__ == '__main__':
    func()
    print 'globals:', globals()

###
func locals: {'x': 123}
globals: {'__builtins__': <module '__builtin__' (built-in)>, 's': 'hello world', 'func': <function func at 0x02CDFBB0>, 'deepcopy': <function deepcopy at 0x02CDF970>, '__name__': '__main__', 'copy': <module 'copy' from 'C:\Python25\lib\copy.pyc'>, '__doc__': None}

从输出结果可以看出globals()包含了定义的函数,变量等。对于’deepcopy’: 可以看出deepcopy已经被导入到自己的名字空间了,而不是在copy里面。
而导入的import copy则还保留着自身的名字空间。因此要访问copy的方法,就需要使用copy.function了。
这也就是为什么推荐使用import module的原因,因为from A import B这样会把B引入自身的名字空间,容易发生覆盖或者说污染。

生存周期

每个名字空间都有自己的生存周期,如下:
- builtins: 在python解释器启动的时候,便已经创建,直到退出
- globals: 在模块定义被读入时创建,通常也一直保存到解释器退出。
- locals : 在函数调用时创建, 直到函数返回,或者抛出异常之后,销毁。 
- 另外递归函数每一次均有自己的名字空间。

看着没有问题,但是有很多地方需要考虑。比如名字空间都是在代码编译时期确定的,而不是执行期间。这个也就可以解释为什么在例1中,before func2:的locals()里面包含了x: 1 这一项。再看下面这个,

def func():
    if False:
        x = 10 #该语句永远不执行
    print x

###
Traceback (most recent call last):
  File "C:\Desktop\b.py", line 6, in <module>
    func()
  File "C:\Desktop\b.py", line 4, in func
    print x
UnboundLocalError: local variable 'x' referenced before assignment

虽然x = 10永远不会执行,但是在执行之前的编译阶段,就会把x作为locals变量,但是后面编译到print的时候,发现没有赋值,因此直接抛出异常,locals()里面便不会有x。这个就跟例子2中,before func2里面没有x是一个道理。

赋值

为什么要把赋值单独列出来呢,因为赋值操作对名字空间的影响很大,而且很多地方需要注意。 核心就是: 赋值修改的是命名空间,而不是对象, 比如:
a=0
这个语句就是把a放入到了对应的命名空间, 然后让它指向一个值为10的整数对象。
a = []
a.append(1)
这个就是把a放入到名字空间,然后指向一个列表对象, 然而后面的a.append(1)这句话只是修改了list的内容,并没有修改它的内存地址。因此 并没有涉及到修改名字空间。 赋值操作有个特点就是: 赋值操作总是在最里层的作用域.也就说,只要编译到了有赋值操作,就会在当前名字空间内新创建一个名字,然后开始才绑定对象。即便该名字已存在于赋值语句发生的上一层作用域中;

总结

现在再看例子2, 就清晰多了, x += x 编译到这里时,发现了赋值语句,于是准备把x新加入最内层名字空间也就是func2中,即使上层函数已经存在了; 但是赋值的时候,又要用到x的值, 然后就会报错:

这样看起来好像就是 内部函数只可以读取外部函数的变量,而不能做修改,其实本质还是因为赋值涉及到了新建locals()的名字。 在稍微改一点:

!/usr/bin/env python
# encoding: utf-8
def func1():
    x = [1,2]
    print 'before func1:', locals()
    def func2():
        print 'before fun2:', locals()
        x[0] += x[0] #就是这里使用x[0]其余地方不变
        print 'after fun2:', locals()
    func2()
    print 'after func1:', locals()
if __name__ == '__main__':
    func1()

咋正确了呢—这不应该要报错吗? 其实不然,就跟上面的a.append(1)是一个道理。 x[0] += x[0] 这个并不是对x的赋值操作。
按照LEGB原则, 搜到func1有变量x并且是个list, 然后将其加入到自己的locals(), 后面的x[0] += x[0], 就开始读取x的元素,并没有影响func2的名字空间。另外无论func1与func2的名字空间的x 没有什么关系,只不过都是对[1, 2]这个列表对象的一个引用。

这个例子其实也给了我们一个启发,我们知道内部函数无法直接修改外部函数的变量值,如例2,如果借助list的话, 就可以了吧!比如把想要修改的变量塞到一个list里面,然后在内部函数里面做改变!当然python3.x里面有了nonlocal关键字,直接声明一下就可以修改了。

Reference

本文由 shomy 发表于 个人博客
非商业转载请注明作者及出处。商业转载请联系作者本人。
标题为: 由一个例子到python的名字空间
链接为: http://shomy.top/2016/03/01/python-namespace-1/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值