标签:
Python的变量定义后都有自己的作用域,每个作用域内都有名字空间。名称空间就是变量名称与对象的关联关系。Python中使用变量名引用对象,需要使用该变量时,就在命名空间中进行搜索,获取对应的对象。从目前python的实现上来讲,内部使用了字典,但是并不保证以后会更改实现,所以说现阶段,命名空间是一个字典(dictionary),它的键就是变量名,它的值就是那些变量的值。在一个Python程序运行中,至少有4个scopes是存在的。
直接访问一个变量可能在这四个namespace中逐一搜索。
包含局部变量。
比如一个函数/方法内部。
包含了非局部(non-local)也非全局(non-global)的变量。
比如两个嵌套函数,内层函数可能搜索外层函数的namespace,但该namespace对内层函数而言既非局部也非全局。
当前脚本的最外层。
比如当前模块的全局变量。
Python __builtin__
模块。
包含了内建的变量/关键字等。
那么,这么多的作用域,Python是按什么顺序搜索对应作用域的呢?
著名的”LEGB-rule”,即scope的搜索顺序:
Local -> Enclosing -> Global -> Built-in
每个函数都有着自已的名称空间,叫做局部名称空间;
每个局部名称空间的外部的名称空间,叫做封闭区域;如内嵌函数的外部函数的局部名称空间,就是这个内嵌函数的封闭区域。
每个模块拥有它自已的名称空间,叫做全局名称空间;
还有就是内置名称空间,任何模块均可访问它,它存放着内置的函数和异常。
当有一个变量在 local 域中找不到时,Python会找上一层的作用域,即 enclosing 域(该域不一定存在)。
enclosing 域还找不到的时候,再往上一层,搜索模块内的 global 域。最后,会在 built-in 域中搜索。
对于最终没有搜索到时,Python会抛出一个 NameError
异常。
嵌套函数的情况:
1. 全局作用域有全局名字空间,在程序开始运行时生成,在程序退出时消失,可以通过globals()函数或dir("__main__")来访问全局名字空间。
2. 函数定义的函数体内就是局部名字空间,在调用函数时生成,在调用结束就消失,在函数体内调用locals()可以访问局部名字空间。Python中有一个现象非常奇特,在函数体内对全局变量进行赋值时需要使用global关键字进行变量声明,这是因为Python中有一个局部赋值规则造成的。比如说:
globalVar = 2 def myFunc(): globalVar = globalVar + 2 print globalVar
上述代码就会抛出“UnboundLocalError: local variable ‘globalVar‘ referenced before assignment”的异常,要求变量必须先赋值。
如果在函数体内对变量进行赋值,python将名称添加到局部名字空间中,右侧执行运算时,发现在局部名字空间中有globalVar,就尝试将globalVar加1,而局部名字空间中的globalVar尚未赋值,就会抛出异常。
globalVar = 2 def myFunc(): global globalVar globalVar = globalVar + 2 print globalVar
将globalVar声明为全局变量就会解决上述问题。
3. Encolsing Function变量的名字空间。简单的说就是在函数体内定义的新函数的本地名字空间。
globalVar = 2 def myFunc(): funVar = 3 def innerFunc(): innerVar = 4 innerVar2 = funVar print ‘inner locals:‘ + str(locals()) #print ‘inner globals:‘ + str(globals()) innerFunc() print ‘outer locals:‘ + str(locals()) #print ‘outer globals:‘ +str(globals())
此处略去了global函数的输出:
inner locals:{‘funVar‘: 3, ‘innerVar2‘: 3, ‘innerVar‘: 4} outer locals:{‘funVar‘: 3, ‘innerFunc‘: <function innerFunc at 0x0000000007BBC4A8>}
可以看出内部函数名是外部函数本地名字空间的一部分,内部函数在查找名字时会先查找本地名字空间,然后是外部函数的本地名字空间(封闭区域enclosing),再就是全局变量,最后是在内置模块名字空间中查找。
4. 内置模块的名字空间。python在启动时,会自动创建__builtins__模块,在该模块中定义了python数据类型。遵循LEGB搜索规则,如果Python不能在本地名字空间中找到某个名称,就会在全局命名空间中继续寻找,最后内置模块中查找。可以通过__builtins__.__dict__获取内置模块的名字。
首先要知道:从某种意义上来说,一个对象(object)的所有属性(attribute)也构成了一个namespace.
a. 关于类类型和类实例对象的属性查找方法。比如以下的定义:
class MyBase(object): pass MyBase.attrA = ‘foo‘ MyBase.attrB = ‘bar‘ baseObj = MyBase() baseObj.attrA = ‘hello‘ baseObj.attrC = ‘world‘ print baseObj.attrA # print hello print baseObj.attrB # print bar print MyBase.attrC # AttributeError
如果在实例对象中找不到相关的属性就在类类型的名字空间中查找,但是反过来,在类类型中找不到的属性,不会向关联的实例对象名字空间中进行查找。这种情况也比较容易理解,毕竟类类型与类实例是一对多的关系。
这里另外有个微妙的地方需要强调。实例对象的属性一旦定义就会覆盖类类型名字空间中定义的属性。看下面的例子:
class Base(object): var = ‘hello‘ obj1 = Base() obj1.var = ‘world‘ obj2 = Base() print ‘obj1 value = ‘ + str(obj1.var) + ‘| id = ‘ + str(id(obj1.var)) print ‘obj2 value = ‘ + str(obj2.var) + ‘| id = ‘ + str(id(obj2.var)) print ‘Base value = ‘ + str(Base.var) + ‘| id = ‘ + str(id(Base.var))
执行结果:
b. 子类实例对象的属性如果在实例对象中查找不到,先在子类类型名字空间中查找,再向父类的名字空间中进行查找。
class PythonBase(object): varBase = ‘foo‘ class Derived(PythonBase): pass derivedObj = Derived() print derivedObj.varBase
标签:
原文地址:http://www.cnblogs.com/stemon/p/5098236.html