专栏名称: 程序员大咖
为程序员提供最优质的博文、最精彩的讨论、最实用的开发资源;提供最新最全的编程学习资料:PHP、Objective-C、Java、Swift、C/C++函数库、.NET Framework类库、J2SE API等等。并不定期奉送各种福利。
目录
相关文章推荐
程序员的那些事  ·  为被榨干!英伟达下场优化 ... ·  20 小时前  
程序员之家  ·  急了!ChatGPT开放色情内容生成。。。 ·  4 天前  
51好读  ›  专栏  ›  程序员大咖

由一个例子到 python 的名字空间

程序员大咖  · 公众号  · 程序员  · 2016-12-22 17:31

正文

点击上方 蓝色字体 关注「程序员大咖」

来源:shomy

链接:shomy.top/2016/03/01/python-namespace-1/


例子引入


例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 ()


可以正常输出结果: 并且需要注意,在func2使用x变量之前的名字空间就已经有了'x':1.


before func1 : { 'x' : 1 }

before fun2 : { 'a' : 1 , 'x' : 1 }

after fun2 : { 'a' : 2 , 'x' : 1 }

after func1 : { 'x' : 1 , 'func2' : function func2 at 0x7f7c89700b90 > }


稍微改一点:如下


例2:


#!/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 ()


输出就开始报错: 而且在before func2也没有了x.


before func1 : { 'x' : 1 }

before fun2 : {}

Traceback ( most recent call last ) :

File "test.py" , line 18 , in module >

func1 ()

File "test.py" , line 14 , in func1

func2 ()

File "test.py" , line 11 , in func2

x += x

UnboundLocalError : local variable 'x' referenced before assignment


这两个例子正好涉及到了python里面最核心的内容:名字空间,正好总结一下,然后在解释这几个例子。


名字空间(Namespace)


比如我们定义一个”变量”


In [ 14 ] : a

NameError : name 'a' is not defined


所以,这里更准确的叫法应该是名字。 一些语言中比如c,c++,java 变量名是内存地址别名, 而Python 的名字就是一个字符串,它与所指向的目标对象关联构成名字空间里面的一个键值对{name: object},因此可以这么说,python的名字空间就是一个字典.。


分类


python里面有很多名字空间,每个地方都有自己的名字空间,互不干扰,不同空间中的两个相同名字的变量之间没有任何联系一般有4种:LEGB四种


  • locals: 函数内部的名字空间,一般包括函数的局部变量以及形式参数

  • enclosing function: 在嵌套函数中外部函数的名字空间, 对fun2来说, fun1的名字空间就是。

  • globals: 当前的模块空间,模块就是一些py文件。也就是说,globals()类似全局变量。

  • __builtins__: 内置模块空间, 也就是内置变量或者内置函数的名字空间。


当程序引用某个变量的名字时,就会从当前名字空间开始搜索。搜索顺序规则便是: LEGB.


locals  -> enclosing function -> globals -> __builtins


一层一层的查找,找到了之后,便停止搜索,如果最后没有找到,则抛出在NameError的异常。这里暂时先不讨论赋值操作。

比如例1中的a = x + 1 这行代码,需要引用x, 则按照LEGB的顺序查找,locals()也就是func2的名字空间没有,进而开始E,也就是func1,里面有,找到了,停止搜索,还有后续工作,就是把x也加到自己的名字空间,这也是为什么fun2的名字空间里面也有x的原因。


访问方式


其实上面都已经说了,这里暂时简单列一下


  1. 使用locals()访问局部命名空间

  2. 使用globals()访问全局命名空间


这里有一点需要注意,就是涉及到了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 ) > ,

'__file__' : 'test.py' ,

'__package__' : None ,

's' : 'hello world' ,

'func' : function func at 0x7f1c3d617c80 > ,

'deepcopy' : function deepcopy at 0x7f1c3d6177d0 > ,

'__name__' : '__main__' ,

'copy' : module 'copy' from '/usr/lib/python2.7/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


肯定会报错的,但是错误不是


NameError: global name 'x' is not defined


而是:


UnboundLocalError: local variable 'x' referenced before assignment


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


赋值


为什么要把赋值单独列出来呢,因为赋值操作对名字空间的影响很大,而且很多地方需要注意。


核心就是: 赋值修改的是命名空间,而不是对象 , 比如:


a = 10


这个语句就是把a放入到了对应的命名空间, 然后让它指向一个值为10的整数对象。


a = []

a . append ( 1 )


这个就是把a放入到名字空间,然后指向一个列表对象, 然而后面的a.append(1)这句话只是修改了list的内容,并没有修改它的内存地址。因此

并没有涉及到修改名字空间。


赋值操作有个特点就是: 赋值操作总是在最里层的作用域.也就说,只要编译到了有赋值操作,就会在当前名字空间内新创建一个名字,然后开始才绑定对象。即便该名字已存在于赋值语句发生的上一层作用域中;


总结


分析例子


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


UnboundLocalError: local variable 'x' referenced before assignment


这样看起来好像就是 内部函数只可以读取外部函数的变量,而不能做修改,其实本质还是因为赋值涉及到了新建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







请到「今天看啥」查看全文