Python中的作用域规则——LEGB原则详解与实例分析
Python中的作用域规则——LEGB原则详解与实例分析
问题
大家先自己做个测试,看看以下代码的输出是什么?
是报错?还是能打印出数值?请在心中默念答案,稍后我们将揭晓正确答案。
print(12)
def a():
print(c)
return 1
if __name__ == '__main__':
c = 2
b = a()
c = 3
b = a()
一、LEGB原则原理
LEGB是Python中变量查找的四个作用域的缩写,当在Python中访问一个变量时,解释器会按照LEGB的顺序来查找变量,即首先在局部作用域中查找,如果找不到,则依次在外部作用域、全局作用域和内置作用域中查找。具体如下:
Local(局部作用域):指的是函数内部的作用域。在函数内部定义的变量都属于局部作用域。
Enclosing(外部作用域):指的是包含函数的局部作用域,但不是全局作用域。这通常出现在嵌套函数的定义中,外部函数的作用域对于内部函数来说就是外部作用域。
Global(全局作用域):指的是模块级别的作用域。在模块顶层定义的变量都属于全局作用域,它们可以被模块中的所有函数访问。
Built-in(内置作用域):指的是Python内置的名称空间,包含了Python解释器内置的名称,如print
、id
等。
二、案例分析
现在,让我们回到开头的问题。正确答案是:
12
2
3
What?不应该报错变量c没有提前命名吗?—— 然而并没有报错。
那为什么是这个答案呢?让我们一步步分析。
print(12)
def a():
print(c)
return 1
if __name__ == '__main__':
c = 2
b = a()
c = 3
b = a()
1.首先,print(12)
会直接输出数字12。接着,定义了一个函数a
,它内部有一个打印变量c
的操作。在函数a
被定义时,变量c
尚未定义,但这不会引发错误,因为变量c
将在函数调用时查找。
2.当执行到if __name__ == '__main__':
块时,首先定义了c = 2
,然后调用a()
。根据LEGB原则,函数a
内部在查找变量c
时,会在全局作用域中找到c
的值为2,并打印出来。
3.然后,c
的值被更新为3,并再次调用a()
。此时,函数a
内部再次查找变量c
,这次找到的值为3,并打印出来。
4.通过这个案例,我们可以看到LEGB原则在实际代码中的作用。函数a
在调用时能够访问到全局作用域中的变量c
,这是因为局部作用域中没有找到c
,所以向上查找到了全局作用域。
三、进一步演示
为了加深理解,我们再看一个例子:
x = 10
def outer():
x = 5
def inner():
x = 3
print("inner:", x)
inner()
print("outer:", x)
outer()
print("global:", x)
这段代码的输出是什么?答案是:
inner: 3
outer: 5
global: 10
在这个例子中,每个函数都定义了自己的局部变量x,并且在各自的局部作用域中打印出来。当我们调用inner()时,它打印的是局部作用域中的x,即3。调用outer()时,它打印的是自己的局部作用域中的x,即5。而在全局作用域中打印的x则是全局变量,值为10。
四、编程建议:
特别注意:在包含if name == '__main__的单个执行文件中,函数中的变量 要和if name == '__main__部分的变量名称作区别。(这种情况特别容易出现在单个文件的代码执行,特别是在使用if name == ‘main’:时,对其中的代码封装成函数,又没有在函数指定参数时,就会发生让自己吃惊的情况:What,这种代码不报错吗?)
避免变量名冲突:在不同的作用域中,尽量避免使用相同的变量名。如果局部作用域中的变量与全局作用域中的变量同名,它可能会无意中覆盖全局变量,导致不可预见的错误。
明确的作用域:尽量在函数内部定义所需的变量,这样可以使变量作用域更加明确,减少潜在的命名冲突。
使用描述性的变量名:选择清晰、具有描述性的变量名,这样即使在不同作用域中有相同的变量名,也能够通过名字的含义来区分它们。
封装和模块化:将代码分割成模块和函数,每个模块或函数负责一个具体的功能。这样可以在不同的模块或函数中使用相同的变量名,而不会引起冲突。
谨慎使用全局变量:尽量避免使用全局变量,因为它们可以在代码的任何地方被修改,这会增加代码的复杂性和出错的可能性。
遵循命名约定:对于全局变量,可以使用全大写字母来命名,这样可以在视觉上区分它们与局部变量。
使用函数参数:如果需要在函数内部使用外部作用域的变量,可以通过函数参数的方式传递,而不是直接在函数内部访问。
了解LEGB原则:深入理解Python的LEGB作用域规则,这有助于你更好地控制变量的作用域,避免不必要的错误。
五、总结
在编程实践中,良好的命名习惯和作用域管理是编写清晰、可维护代码的关键。通过遵循上述建议,你可以减少代码中的混淆和错误,提高代码的可读性和稳定性。记住,清晰的代码结构不仅有助于他人理解你的代码,也方便你未来的代码维护。
作者:landerous