Python中的LEGB规则
目标
- 命名空间和作用域——Python从哪里查找变量名?
- 我们能否同时定义或使用多个对象的变量名?
- Python查找变量名时是按照什么顺序搜索不同的命名空间?
命名空间与作用域的介绍
命名空间
大约来说,命名空间就是一个容器,其中包含的是映射到不同对象的名称。你可能已经听说过了,Python中的一切——常量,列表,字典,函数,类,等等——都是对象。
这样一种“名称-对象”间的映射,使得我们可以通过为对象指定的名称来访问它。举例来说,如果指定一个简单的字符串a_string = "Hello string"
,我们就创建了一个对象“Hello string”的引用,之后我们就可以通过它的名称a_string
来访问它。
我们可以把命名空间描述为一个Python字典结构,其中关键词代表名称,而字典值是对象本身(这也是目前Python中命名空间的实现方式),如:
a_namespace = {'name_a':object_1, 'name_b':object_2, ...}
现在比较棘手的是,我们在Python中有多个独立的命名空间,而且不同命名空间中的名称可以重复使用(只要对象是独一无二的),比如:
a_namespace = {'name_a':object_1, 'name_b':object_2, ...}
b_namespace = {'name_a':object_3, 'name_b':object_4, ...}
举例来说,每次我们调用for循环或者定义一个函数的时候,就会创建它自己的命名空间。命名空间也有不同的层次(也就是所谓的“作用域”),我们会在下一节详细讨论。
作用域
在上一节中,我们已经学习到命名空间可以相互独立地存在,而且它们被安排在某个特定层次,由此引出了“作用域”的概念。Python中的“作用域”定义了一个“层次”,我们从其中的命名空间中查找特定的“名称-对象”映射对。
举例来说,我们来考虑一下下面的代码:
i = 1
def foo():
i = 5
print(i, 'in foo()')
print(i, 'global')
foo()
1 global
5 in foo()
我们刚刚两次定义了变量名i,其中一次是在函数foo内。
- foo_namespace = {'i':object_3, ...}
- global_namespace = {'i':object_1, 'name_b':object_2, ...}
这样的话,如果我们要打印变量i的值,Python如何知道应该搜索哪个命名空间呢?到此LEGB规则就开始起作用了,我们将在下一节进行讨论。
提示:
如果我们想要打印出全局变量与局部变量的字典映射,我们可以使用函数globals()
和locals()
:
#print(globals()) # prints global namespace
#print(locals()) # prints local namespace
glob = 1
def foo():
loc = 5
print('loc in foo():', 'loc' in locals())
foo()
print('loc in global:', 'loc' in globals())
print('glob in global:', 'foo' in globals())
loc in foo(): True
loc in global: False
glob in global: True
通过LEGB规则对变量名进行作用域解析
我们已经知道了多个命名空间可以独立存在,而且可以在不同的层次上包含相同的变量名。“作用域”定义了Python在哪一个层次上查找某个“变量名”对应的对象。接下来的问题就是:“Python在查找‘名称-对象’映射时,是按照什么顺序对命名空间的不同层次进行查找的?”
答案就是:使用的是LEGB规则,表示的是Local -> Enclosed -> Global -> Built-in,其中的箭头方向表示的是搜索顺序。
- Local 可能是在一个函数或者类方法内部。
- Enclosed 可能是嵌套函数内,比如说 一个函数包裹在另一个函数内部。
- Global 代表的是执行脚本自身的最高层次。
- Built-in 是Python为自身保留的特殊名称。
因此,如果某个name:object映射在局部(local)命名空间中没有找到,接下来就会在闭包作用域(enclosed)进行搜索,如果闭包作用域也没有找到,Python就会到全局(global)命名空间中进行查找,最后会在内建(built-in)命名空间搜索(注:如果一个名称在所有命名空间中都没有找到,就会产生一个NameError)。
注意:
命名空间也可以进一步嵌套,例如我们导入模块时,或者我们定义新类时。在那些情形下,我们必须使用前缀来访问那些嵌套的命名空间。我用下面的代码来说明:
import numpy
import math
import scipy
print(math.pi, 'from the math module')
print(numpy.pi, 'from the numpy package')
print(scipy.pi, 'from the scipy package')
3.141592653589793 from the math module
3.141592653589793 from the numpy package
3.141592653589793 from the scipy package
(这也就是为什么当我们通过“from a_module import *”导入模块时需要格外小心,因为这样会把变量名加载到全局命名空间中,而且可能覆盖已经存在的变量名)
1. LG - 局部与全局作用域
例 1.1
作为热身练习,我们先忘记LEGB规则中的外围函数(E)和内建(B)两个作用域,只考虑LG——局部与全局作用域。
下面的代码输出是怎样的?
a_var = 'global variable'
def a_func():
print(a_var, '[ a_var inside a_func() ]')
a_func()
print(a_var, '[ a_var outside a_func() ]')
a) raise an error
b) global value [ a_var outside a_func() ]
c)
global value [ a_var inside a_func() ]
global value [ a_var outside a_func() ]
解析:
我们首先调用了a_func(),其中要打印a_var的值。根据LEGB规则,函数会首先搜索它自身的局部作用域(L),查看是否定义了a_var。因为a_func()没有定义自己的a_var,它将会在上一层的全局作用域(G)中进行搜索,其中已经定义了a_var。
例 1.2
现在,我们在全局作用域和局部作用域中都定义变量a_var。你是否知道下面代码的结果?
a_var = 'global value'
def a_func():
a_var = 'local value'
print(a_var, '[ a_var inside a_func() ]')
a_func()
print(a_var, '[ a_var outside a_func() ]')
a)
raises an error
b)
local value [ a_var inside a_func() ]
global value [ a_var outside a_func() ]
c)
global value [ a_var inside a_func() ]
global value [ a_var outside a_func() ]
解析:
当我们调用a_func()时,首先会在局部作用域中查找a_var,因为a_var已经在局部作用域进行了定义,所以它在局部作用域所赋的值就会被打印出来。注意这并不会影响全局变量,因为是在不同的作用域当中。
不过,如果使用global关键字,也可以修改全局变量。如下所示:
a_var = 'global value'
def a_func():
global a_var
a_var = 'local value'
print(a_var, '[ a_var inside a_func() ]')
print(a_var, '[ a_var outside a_func() ]')
a_func()
print(a_var, '[ a_var outside a_func() ]')
global value [ a_var outside a_func() ]
local value [ a_var inside a_func() ]
local value [ a_var outside a_func() ]
但是我们必须注意顺序:如果我们没有明确地告诉Python我们要使用的是全局作用域,而是直接尝试修改变量值的话,就很容易产生UnboundLocalError。(记住,赋值操作的右半部分是先执行的)
a_var = 1
def a_func():
a_var = a_var + 1
print(a_var, '[ a_var inside a_func() ]')
print(a_var, '[ a_var outside a_func() ]')
a_func()
a_var = 1
def a_func():
a_var = a_var + 1
print(a_var, '[ a_var inside a_func() ]')
print(a_var, '[ a_var outside a_func() ]')
a_func()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-4-a6cdd0ee9a55> in <module>()
6
7 print(a_var, '[ a_var outside a_func() ]')
----> 8 a_func()
<ipython-input-4-a6cdd0ee9a55> in a_func()
2
3 def a_func():
----> 4 a_var = a_var + 1
5 print(a_var, '[ a_var inside a_func() ]')
6
UnboundLocalError: local variable 'a_var' referenced before assignment
1 [ a_var outside a_func() ]
2. LEG - 局部,闭包与全局作用域
现在我们引入外围函数(E)作用域的概念。根据顺序“Local-> Enclosed -> Global”,你能否猜出下面代码的输出结果?
例 2.1
a_var = 'global value'
def outer():
a_var = 'enclosed value'
def inner():
a_var = 'local value'
print(a_var)
inner()
outer()
a)
global value
b)
enclosed value
c)
local value
解析:
我们来快速总结一下刚才做了什么:我们调用了outer(),它定义了一个局部变量a_var(在全局作用域已经存在一个a_var)。接下来,outer()函数调用了inner(),该函数也定义了一个名称为a_var的变量。在inner()内的print()函数首先在局部作用域内搜索(L->E),因此会打印出在局部作用域内所赋的值。
类似于上一节所说的global关键字,我们也可以在内部函数中使用nonlocal关键字来明确地访问外部(外围函数)作用域的变量,也可以修改它的值。
注意nonlocal关键字是在Python 3.x才新加的,而且在Python 2.x中没有实现(目前还没有)。
a_var = 'global value'
def outer():
a_var = 'local value'
print('outer before:', a_var)
def inner():
nonlocal a_var
a_var = 'inner value'
print('in inner():', a_var)
inner()
print("outer after:", a_var)
outer()
outer before: local value
in inner(): inner value
outer after: inner value
3. LEGB - 局部,外围,全局,内建
为了完整理解LEGB规则,我们来学习内建作用域。在这里,我们定义“自己的”长度函数,碰巧跟内建的len()函数同名。如果我们执行下面的代码,你认为输出结果是什么?
例 3
a_var = 'global variable'
def len(in_var):
print('called my len() function')
l = 0
for i in in_var:
l += 1
return l
def a_func(in_var):
len_in_var = len(in_var)
print('Input variable is of length', len_in_var)
a_func('Hello, World!')
a)
raises an error (conflict with in-built `len()` function)
b)
called my len() function
Input variable is of length 13
c)
Input variable is of length 13
解析:
因为完全相同的名称也可以映射到不同的对象——只要名称是在不同的命名空间中——因此重新使用名称len来定义我们自己的长度函数是没有问题的(这只是为了示范,不是必须的)。我们在Python中按照L -> E -> G -> B层次进行搜索,a_func()函数在尝试搜索内建(B)命名空间之前,首先会在全局作用域(G)中发现len()。
自我评估练习
现在我们已经完成了一些练习,我们来快速地检查一下效果。那么再问一次:下面的代码会输出什么?
a = 'global'
def outer():
def len(in_var):
print('called my len() function: ', end="")
l = 0
for i in in_var:
l += 1
return l
a = 'local'
def inner():
global len
nonlocal a
a += ' variable'
inner()
print('a is', a)
print(len(a))
outer()
print(len(a))
print('a is', a)
a is local variable
called my len() function: 14
6
a is global
总结
我希望这个简短的教程能有助于理解Python中的一个基本概念,即使用LEGB规则的作用域解析顺序。我鼓励你明天重新查看一下这些代码片段(作为一个小的自我评估),检查一下你是否可以准确预测所有的输出值。
经验法则
在实际中,在函数作用域内修改全局变量通常是个坏主意,因为这经常造成混乱或者很难调试的奇怪错误。如果你想要通过一个函数来修改一个全局变量,建议把它作为一个变量传入,然后重新指定返回值。
例如:
a_var = 2
def a_func(some_var):
return 2**3
a_var = a_func(a_var)
print(a_var)
8
答案
为了防止你无意中看到,我把答案写成了二进制形式。如果要显示成字符形式,你只需要执行下面的代码行:
print('Example 1.1:', chr(int('01100011',2)))
print('Example 1.2:', chr(int('01100010',2)))
print('Example 2.1:', chr(int('01100011',2)))
print('Example 3.1:', chr(int('01100010',2)))
# Execute to run the self-assessment solution
sol = "000010100110111101110101011101000110010101110010001010"\
"0000101001001110100000101000001010011000010010000001101001011100110"\
"0100000011011000110111101100011011000010110110000100000011101100110"\
"0001011100100110100101100001011000100110110001100101000010100110001"\
"1011000010110110001101100011001010110010000100000011011010111100100"\
"1000000110110001100101011011100010100000101001001000000110011001110"\
"1010110111001100011011101000110100101101111011011100011101000100000"\
"0011000100110100000010100000101001100111011011000110111101100010011"\
"0000101101100001110100000101000001010001101100000101001100001001000"\
"0001101001011100110010000001100111011011000110111101100010011000010"\
"1101100"
sol_str =''.join(chr(int(sol[i:i+8], 2)) for i in range(0, len(sol), 8))
for line in sol_str.split('\n'):
print(line)
警告:For循环变量“泄漏”到全局命名空间
与其它一些编程语言不同,Python中的for循环会使用它所在的作用域,而且把它所定义的循环变量加在后面。
for a in range(5):
if a == 4:
print(a, '-> a in for-loop')
print(a, '-> a in global')
4 -> a in for-loop
4 -> a in global
如果我们提前在全局命名空间中明确定义了for循环变量,也是同样的结果!在这种情况下,它会重新绑定已有的变量:
b = 1
for b in range(5):
if b == 4:
print(b, '-> b in for-loop')
print(b, '-> b in global')
4 -> b in for-loop
4 -> b in global
不过,在Python 3.x中,我们可以使用闭包来防止for循环变量进入全局命名空间。下面是一个例子(在Python 3.4中执行):
i = 1
print([i for i in range(5)])
print(i, '-> i in global')
[0, 1, 2, 3, 4]
1 -> i in global
为什么我要强调“Python 3.x”?因为在Python 2.x下执行同样的代码,打印结果是:
4 -> i in global
这是因为在Python 3.x中所做的一个变化,在What’s New In Python 3.0中有如下描述:
“列表推导式不再支持 [... for var in item1, item2, ...]
这样的语法形式,取而代之的是 [... for var in (item1, item2, ...)]
。也要注意列表推导式有不同的语义: 它们更接近于一个 list()
构造器内的生成器表达式的语法糖,特别是循环控制变量不再泄漏到外围作用域中。”
原文:https://www.cnblogs.com/GuoYaxiang/p/6405814.html
Python中的LEGB规则的更多相关文章
- python——作用域之LEGB规则
1 变量的作用域 Python是静态作用域,也就是说在Python中,变量的作用域源于它在代码中的位置:在不同的位置,可能有不同的命名空间.命名空间是变量作用域的体现形式. 2 LEGB各自代表的含义 ...
- python作用域与LEGB规则
作用域 什么是命名空间 比如有一个学校,有10个班级,在7班和8班中都有一个叫“小王”的同学,如果在学校的广播中呼叫“小王”时,7班和8班中的这2个人就纳闷了,你是喊谁呢!!!如果是“7班的小王”的话 ...
- 理解Python中的继承规则和继承顺序
先来看一段代码: class First(object): def __init__(self): print ("first") class Second(object): de ...
- for和while——python中的循环控制语句详解
循环语句在绝大多数的语言中,都是必不可少的一种控制语句,循环语句允许我们执行一个语句或语句组多次.在python中有for循环和while循环两种,讲到这里,就不得不提到我们的迭代器对象 迭代器 迭代 ...
- 【Python】 命名空间与LEGB规则
命名空间与LEGB规则 之前隐隐约约提到过一些关于Python赋值语句的特殊性的问题,这个问题的根源就在于Python中的变量的命名空间机制和之前熟悉的C也好java也好都不太一样. ■ 命名空间 所 ...
- Python 的 LEGB 规则(转载)
转载:https://mp.weixin.qq.com/s?timestamp=1498528588&src=3&ver=1&signature=DfFeOFPXy44ObCM ...
- 详解 Python 中的下划线命名规则
在 python 中,下划线命名规则往往令初学者相当 疑惑:单下划线.双下划线.双下划线还分前后……那它们的作用与使用场景 到底有何区别呢?今天 就来聊聊这个话题. 1.单下划线(_) 通常情况下,单 ...
- python中对变量的作用域LEGB、闭包、装饰器基本理解
一.作用域 在Python程序中创建.改变.查找变量名时,都是在一个保存变量名的空间中进行,我们称之为命名空间,也被称之为作用域.python的作用域是静态的,在源代码中变量名被赋值的位置决定了该变量 ...
- Python中比较运算符连用的语法规则
在Python中,比较运用符<.>.<=.>=.== .!=可以连用,但语法规则和其它编程语言不一样 以 == 为例,具体语法规则是: a == b == c == d 等价于 ...
随机推荐
- NBUT 1222 English Game 2010辽宁省赛
Time limit 1000 ms Memory limit 131072 kB This English game is a simple English words connection gam ...
- 题目1001:A+B for Matrices
题目1001:A+B for Matrices 时间限制:1 秒内存限制:32 兆 题目描述: This time, you are supposed to find A+B where A and ...
- LUN挂载到Linux主机后,如何对磁盘进行分区
将阵列上的LUN挂载到Linux主机后,如何对磁盘进行分区,方法参考https://www.ibm.com/developerworks/cn/linux/l-lpic1-v3-104-1/ fdis ...
- android系列9.LinearLayout学习
<!-- <LinearLayout> 线性版面配置,在这个标签中,所有元件都是按由上到下的排队排成的 --> <LinearLayout xmlns:android=& ...
- stm32 SPI介绍和配置
SPI是一种高速的,全双工同步的通信总线,在芯片管脚上占用了四根线,节约了芯片的管脚,同时为PCB的布局节省了空间,提供了方便,因此越来越多的芯片集成了这种通信协议,STM32也就有了SPI接口. 有 ...
- [LeetCode&Python] Problem 412. Fizz Buzz
Write a program that outputs the string representation of numbers from 1 to n. But for multiples of ...
- 2243: [SDOI2011]染色(LCT)
2243: [SDOI2011]染色 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 10909 Solved: 4216[Submit][Statu ...
- Ubuntu配置操作总结
一.fstab和mtab的区别 /etc/fstab:是开机自动挂载的配置文件,在开机时起作用.相当于启动linux的时候,自动使用检查分区的fsck命令和挂载分区的mount命令,检查分区和挂载分区 ...
- ubuntu14.04下安装ffmpeg
ubuntu14.04下安装ffmpeg 一.安装各种依赖包 1.yasm(libx264需要依赖yasm) sudo apt-get install yasm 2.libx264 sudo apt- ...
- MySQL--修改MySQL账号密码
##使用mysqladmin进行修改 mysqladmin -u username -h hostname password 'new password'; ##使用set命令进行修改 SET PAS ...