深入理解Python中命名空间的查找规则LEGB
名字空间
Python的名字空间是Python一个非常核心的内容。
其他语言中如C中,变量名是内存地址的别名,而在Python中,名字是一个字符串对象,它与他指向的对象构成一个{name:object}关联。
Python由很多名字空间,而LEGB则是名字空间的一种查找规则。
作用域
Python中name-object的关联存储在不同的作用域中,各个不同的作用域是相互独立的。而我们就在不同的作用域中搜索name-object。
举个栗子,来说明作用域是相互独立的。
In[11]:i="G" In[12]:deftest(): i="L" printi,"inlocals" ....: In[13]:test() Linlocals In[14]:printi,"inglobals" Ginglobals
在上面的栗子中,我们定义了两次i,在test函数中是i-L,在外面是i-G。为什么在test函数中,我们i指向的是对象L,而在外面,i指向的则是G?这就是LEGB的作用。
简述
简而言之,LEGB代表名字查找顺序:locals->enclosingfunction->globals->__builtins__
- locals是函数内的名字空间,包括局部变量和形参
- enclosing外部嵌套函数的名字空间(闭包中常见)
- globals全局变量,函数定义所在模块的名字空间
- builtins内置模块的名字空间
所以,在Python中检索一个变量的时候,优先回到locals里面来检索,检索不到的情况下会检索enclosing,enclosing没有则到globals全局变量里面检索,最后是到builtins里面来检索。
当然,因为builtins的特殊性,我们可以直接在builtins里面添加变量,这样就可以在任意模块中访问变量,不过这种方法太过于变态,不推荐这么做。
locals,globals
函数的形参跟内部变量都存储在locals中。
In[1]:deff(x): ...:a=x ...:printa ...:printlocals() ...: In[2]:f("hello") hello {'a':'hello','x':'hello'}
不过在函数内部调用global声明的时候,可以将变量存储在globals中
In[6]:deff(x): ...:globala ...:a=x ...:printa ...:printlocals() ...: In[7]:f("hello") hello {'x':'hello'} In[8]:printa hello In[9]:printx --------------------------------------------------------------------------- NameErrorTraceback(mostrecentcalllast) <ipython-input-9-2d264e11d975>in<module>() ---->1printx NameError:name'x'isnotdefined
如上面栗子中那样,在函数中声明a为全局变量,则函数f的locals只有参数x,而没有变量,而在外部可以使用变量a,而使用x的时候则是NameError
Enclosed
Enclosing是外部嵌套函数的名字空间。我们经常在闭包中用到。在Python3中提供了一个nonlocal关键字来修改外部嵌套函数的名字空间,但是要使用Python3才有,我等使用Python2的只能眼馋一下。
In[11]:defouter(): ....:a_var='enclosedvalue' ....:printa_var ....:definner(): ....:a_var='localvalue' ....:print(a_var) ....:inner() ....:printa_var ....: In[12]:outer() enclosedvalue localvalue enclosedvalue
下面的栗子简单示范一下nonlocal的用法,实在Python3下面才可以正常运行的:
In[1]:a_var='globalvalue' In[2]:defouter(): ...:a_var="localvalue" ...:print("outerbefor",a_var) ...:definner(): ...:nonlocala_var ...:a_var="innervalue" ...:print("ininner():",a_var) ...:inner() ...:print("outerinner:",a_var) ...: In[3]:outer() outerbeforlocalvalue ininner():innervalue outerinner:innervalue In[4]:print(a_var) globalvalue
builtins
builtins则是内置模块,轻易不要修改
In[19]:b --------------------------------------------------------------------------- NameErrorTraceback(mostrecentcalllast) <ipython-input-19-3b5d5c371295>in<module>() ---->1b NameError:name'b'isnotdefined In[20]:__builtins__.b="builtins" In[21]:b Out[21]:'builtins'
上面栗子中在第一次调用b的时候报错NameError,之后我们修改builtins的名字空间,将名字b与值"builtins"进行关联,就可以正常调用了。这种非常规用法不建议使用。