python变量命名空间LEGB
文档
A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries, but that’s normally not noticeable in any way (except for performance), and it may change in the future. Examples of namespaces are: the set of built-in names (containing functions such as
abs(), and built-in exception names); the global names in a module; and the local names in a function invocation. In a sense the set of attributes of an object also form a namespace. The important thing to know about namespaces is that there is absolutely no relation between names in different namespaces; for instance, two different modules may both define a functionmaximizewithout confusion — users of the modules must prefix it with the module name.名称空间是从名称到对象的映射。目前,大多数名称空间都是作为Python字典实现的,但这在任何情况下都不明显(除了性能),而且将来可能会发生变化。名称空间的例子有:内置名称集(包含abs()等函数和内置异常名称);模块中的全局名称;函数调用中的本地名称。在某种意义上,对象的属性集也构成名称空间。关于名称空间,需要知道的重要一点是,不同名称空间中的名称之间绝对没有关系;例如,两个不同的模块都可以定义一个函数maximizo而不会产生混淆——模块的用户必须在它前面加上模块名。
Although scopes are determined statically, they are used dynamically. At any time during execution, there are at least three nested scopes whose namespaces are directly accessible:
- the innermost scope, which is searched first, contains the local names
- the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contains non-local, but also non-global names
- the next-to-last scope contains the current module’s global names
- the outermost scope (searched last) is the namespace containing built-in names
虽然范围是静态确定的,但是它们是动态使用的。在执行过程中的任何时候,至少有三个嵌套作用域的名称空间是可直接访问的:
- 首先搜索的最内层作用域包含本地名称
- 任何封闭函数的作用域(从最近的封闭作用域开始搜索),包含非本地的,也包括非全局的名称
- 倒数第二个作用域包含当前模块的全局名称。
- 最外层的作用域(最后搜索)是包含内置名称的命名空间
LEGB解释:
- L: locals ,是函数内部变量名空间,包括局部变量和形参
- E: enclosing , 是外部嵌套函数的变量名空间,(使用闭包时函数中嵌套函数)
- G: globals , 全局变量的空间,是函数定义所在模块(一个py文件即是一个模块)的名字空间
- B: builtins , 是内置模块(python解释器解释时即加载,含有很多易用的函数,不用自己写的py文件)的名字空间
Namespaces are created at different moments and have different lifetimes. The namespace containing the built-in names is created when the Python interpreter starts up, and is never deleted. The global namespace for a module is created when the module definition is read in; normally, module namespaces also last until the interpreter quits. The statements executed by the top-level invocation of the interpreter, either read from a script file or interactively, are considered part of a module called
__main__, so they have their own global namespace. (The built-in names actually also live in a module; this is calledbuiltins.)
名称空间在不同的时刻创建,并且具有不同的生存期。包含内置名称的命名空间是在Python解释器启动时创建的,并且永远不会被删除。模块的全局命名空间是在模块定义正常读入时创建的,模块命名空间也会持续到解释器退出为止。解释器的顶层调用执行的语句(从脚本文件中读取的)或交互调用的语句都被认为是一个名为”__ main __”的模块的一部分,因此它们有自己的全局名称空间。(built-in名称实际上也存在于模块中;这叫做builtins
检索一个变量的顺序
检索的顺序决定于 变量所在的位置和LEGB规则,
如果一个变量在一个模块的一个自定义的函数的内部,那么他的检索顺序就是LEGB,即看看函数内部是不是有此变量,找到就不找了,找不到就找是否在外部嵌套的函数中,再看是否在全局变量,最后看内置模块名字空间。
如果一个变量已经是在函数外部了,那么使用时检测顺序就是GB
变量覆盖
如果一个变量在函数内部找不到,他就会逐层往上找;
但是它如果找到了,便不会往上找了,很简单的道理;
然而如果你在函数内部使用了更高层同名的变量,而你还想使用较高层的变量,会发现它被覆盖了。
(python的一切都是对象,函数也是,函数名是一个变量,它绑定了这个函数对象,所以才可以调用(callable))
强龙不压地头蛇,很形象
举例:
1 | |
1 | |
python语句块不作为变量的生存空间
python中只有LEGB,不像C或其他的一些语言:语句块内声明的变量,语句块结束变量也就不能使用了
但python还是有顺序的,假如在函数内部,直接print(i) ,外部EGB都没有i,会显示
“i is not defined”
把 i 写在print(i) 后也不行,必须写在前面
1 | |
1 | |
1 | |
1 | |
global 和 nonlocal关键字
If a name is declared global, then all references and assignments go directly to the middle scope containing the module’s global names. To rebind variables found outside of the innermost scope, the
nonlocalstatement can be used; if not declared nonlocal, those variables are read-only (an attempt to write to such a variable will simply create a new local variable in the innermost scope, leaving the identically named outer variable unchanged).如果名称声明为全局(global)的,然后所有引用和赋值都直接指向包含模块全局名称的中间范围。要重新绑定最内层作用域之外的变量,可以使用 nonlocal 语句;如果没有声明为非本地变量,那么这些变量就是只读的(对这样一个变量进行写操作的尝试只会在最内层的作用域中创建一个新的本地变量,而保留相同名称的外部变量不变)
A special quirk of Python is that – if no
globalstatement is in effect – assignments to names always go into the innermost scope. Assignments do not copy data — they just bind names to objects. The same is true for deletions: the statementdel xremoves the binding ofxfrom the namespace referenced by the local scope. In fact, all operations that introduce new names use the local scope: in particular,importstatements and function definitions bind the module or function name in the local scope.The
globalstatement can be used to indicate that particular variables live in the global scope and should be rebound there; thenonlocalstatement indicates that particular variables live in an enclosing scope and should be rebound there.Python的一个特殊之处是,如果没有全局语句,对名称的赋值总是进入最内层的范围。赋值不复制数据——它们只是将名称绑定到对象。删除也是如此:del x语句从本地范围引用的名称空间中删除x的绑定。事实上,所有引入新名称的操作都使用本地范围:尤其是import语句和函数定义将模块或函数名绑定到本地范围中。
global语句可以用来表示特定的变量位于全局范围内,并且应该在全局范围内进行重新绑定;nonlocal语句表示特定的变量位于一个封闭的范围内,应该在该范围内进行重新绑定
如上的官方文档说的应该很详细了,简而言之, 就是函数内部如果不使用 global声明,修改(重新绑定)不了全局的变量值(绑定的对象),只会创建一个不与全局变量冲突的同名的局部变量,同名的全局变量变量并不会得到修改;在函数内部如果不使用=,即不去绑定对象,使用变量时解释器会按照LEGB顺序找到对应的该变量名的变量来使用
即不使用global,函数内部对全局变量只有可读权限,没有可写权限(针对于对象的绑定而言的)
之所以这么说,是因为一些全局变量绑定了可变类型的对象,如list, 不使用global声明,也可以在函数内部读到全局的变量,并且对该变量所绑定的对象利用对象的自带方法进行改变,只是改变了变量所绑定的对象的各种属性值,而没有改变变量对于此对象的绑定,即没有重新绑定新的对象。所以依然对全局变量是没有写权限的
同样不使用nonlocal,最内层函数内部对外层函数只有可读权限,而没有写权限(针对对象的绑定而言)
global & 可变和不可变对象的绑定
- 没有改变全局变量的绑定(不可变对象),新建了一个local 范围的变量a,绑定了新的对象
1 | |
1 | |
- 改变了全局变量的绑定(不可变对象)
1 | |
1 | |
- 未改变全局变量的绑定(可变对象),但是它改变了全局变量绑定的对象的属性值。
1 | |
1 | |
- 未改变全局变量的绑定(可变对象),也未改变全局变量所绑定的对象的属性值,新建了一个local范围内的变量,绑定了新的对象
1 | |
1 | |
- 改变了全局变量的绑定(可变对象)
1 | |
1 | |
很多人,课程,把可变对象和不可变对象在这块儿分开讨论,其实我们细心就会发现他们的共性,使用global是具有特定的场景的;修改绑定了可变对象的全局变量不需使用global,说的很含糊,或者说是错误的
官方文档对于global,nonlocal的示例
1 | |
1 | |
Note how the local assignment (which is default) didn’t change scope_test’s binding of spam. The nonlocal assignment changed scope_test’s binding of spam, and the global assignment changed the module-level binding.
You can also see that there was no previous binding for spam before the global assignment.
注意,本地赋值(默认值)没有更改scope_test对spam的绑定。非本地赋值更改了spam的scope_test绑定,全局赋值更改了模块级绑定。您还可以看到,在全局分配之前没有针对spam的绑定。