Python 变量作用域
最后修改于 2025 年 4 月 2 日
变量作用域决定了在程序中的哪个位置可以访问一个变量。Python 使用 LEGB(Local, Enclosing, Global, Built-in)规则来进行名称解析。理解作用域对于编写无错误代码至关重要。本指南通过实际示例涵盖了所有作用域类型。正确的作用域管理可以防止命名冲突和意外行为。
LEGB 规则概述
Python 按照 LEGB 顺序(局部、嵌套、全局、内置)来解析变量名。当引用一个变量时,Python 会按顺序搜索这些作用域。本示例演示了每个作用域级别。理解 LEGB 是 Python 编程的基础。
# Global scope
global_var = "I'm global"
def outer_function():
# Enclosing scope
enclosing_var = "I'm enclosing"
def inner_function():
# Local scope
local_var = "I'm local"
print(local_var) # Local
print(enclosing_var) # Enclosing
print(global_var) # Global
print(len) # Built-in (len function)
inner_function()
outer_function()
# Additional example: LEGB in action
x = "global"
def test():
x = "enclosing"
def inner():
x = "local"
print(x) # What gets printed?
inner()
test() # Prints "local"
该示例展示了所有四个 LEGB 作用域。`inner_function` 首先在其局部作用域中找到 `local_var`。`enclosing_var` 来自嵌套的 `outer_function` 作用域。`global_var` 在模块级别的全局作用域中找到。`len` 解析为内置函数。
附加示例演示了 LEGB 的优先级——最内层作用域的变量具有优先权。当执行 `print(x)` 时,Python 在检查嵌套、全局或内置作用域之前会找到“local”。这种层次结构适用于 Python 中的所有名称查找。
全局作用域
全局作用域变量在模块级别定义,并在整个模块中可访问。函数可以读取全局变量,但需要 `global` 关键字才能修改它们。本示例展示了正确的全局变量用法。理解全局变量有助于管理模块范围的状态。
# Global variable
counter = 0
def increment():
global counter # Declare we're using the global counter
counter += 1
def show_counter():
print(f"Counter: {counter}") # Reading global doesn't need declaration
increment()
show_counter() # Counter: 1
increment()
show_counter() # Counter: 2
# Additional example: Shadowing
value = 10
def demo():
value = 20 # Creates a local variable, doesn't modify global
print(f"Local value: {value}")
demo() # Local value: 20
print(f"Global value: {value}") # Global value: 10
`counter` 变量是全局的,在函数之间共享。`increment` 使用 `global` 修改它,而 `show_counter` 只读取它。没有 `global` 声明,Python 会创建一个局部变量。
遮蔽示例表明,在函数内部赋值给一个名称默认会创建一个局部变量,即使存在一个同名的全局变量。全局变量保持不变。这种行为可以防止意外修改全局变量。
局部作用域
局部作用域变量在函数内部定义,并且只能在该函数内部访问。它们仅在函数执行期间存在。本示例演示了局部变量的行为。正确使用局部变量可以促进封装并防止命名冲突。
def calculate():
# Local variables
x = 10
y = 20
result = x + y
print(f"Inside function: {result}")
calculate()
# print(x) # Would raise NameError: x is not defined
# Additional example: Separate local scopes
def func1():
var = "func1 variable"
print(var)
def func2():
var = "func2 variable"
print(var)
func1() # func1 variable
func2() # func2 variable
# Additional example: Temporary variables
def process_data(data):
temp = data * 2 # Local temporary variable
return temp.upper()
print(process_data("test")) # TESTTEST
变量 `x`、`y` 和 `result` 只存在于 `calculate` 函数内部。尝试在外部访问它们会引发 `NameError`。每次函数调用都会创建新的局部变量,即使是递归调用。
函数具有独立的局部作用域——`func1` 和 `func2` 都可以使用 `var` 而不会发生冲突。局部变量非常适合临时计算,例如 `process_data` 中的 `temp`,它在函数结束后消失。
嵌套(非局部)作用域
嵌套作用域是指嵌套函数外部作用域中的变量。`nonlocal` 关键字允许修改这些变量。本示例演示了嵌套函数的作用域。理解嵌套作用域对于闭包和装饰器至关重要。
def outer():
message = "Original" # Enclosing scope variable
def inner():
nonlocal message # Declare we're using the enclosing message
message = "Modified"
print(f"Inner: {message}")
print(f"Before inner: {message}")
inner()
print(f"After inner: {message}")
outer()
# Additional example: Multiple levels
def level1():
x = 1
def level2():
x = 2
def level3():
nonlocal x # Refers to level2's x
x = 3
level3()
print(f"Level2 x: {x}") # Shows modified value
level2()
print(f"Level1 x: {x}") # Original unchanged
level1()
# Additional example: Without nonlocal
def counter():
count = 0
def increment():
# count += 1 # Would raise UnboundLocalError
nonlocal count
count += 1
return count
return increment
c = counter()
print(c(), c(), c()) # 1 2 3
`outer`/`inner` 示例展示了 `nonlocal` 如何允许 `inner` 修改嵌套作用域中的 `message`。如果没有它,Python 会创建一个新的局部变量。
`nonlocal` 搜索的是最近的嵌套作用域,而不是全局作用域。计数器示例演示了一个常见的闭包模式,其中嵌套函数维护状态。这种技术对于创建函数工厂非常强大。
内置作用域
内置作用域包含 Python 的内置函数和异常(如 `len`、`range`、`ValueError`)。这些名称始终可用。本示例展示了内置作用域的交互。理解内置项可以防止意外的遮蔽。
# Built-in functions
print(len("Python")) # 6
print(max(1, 5, 2)) # 5
# Shadowing built-ins (generally discouraged)
def test():
len = 10 # Shadows built-in len locally
print(len) # 10
test()
print(len("test")) # Still works globally
# Additional example: Restoring access
def custom_len():
# Can still access built-in if needed
orig_len = __builtins__.len
print(orig_len("hello")) # 5
custom_len()
# Additional example: Common shadowing mistakes
def calculate(input):
sum = 0 # Shadows built-in sum()
for num in input:
sum += num
# return sum(input) # Would error
return sum
print(calculate([1, 2, 3])) # 6
内置项在任何地方都可用,除非被遮蔽。`test` 函数演示了局部变量如何在函数作用域内遮蔽内置名称,但在全局范围内,内置项仍然可访问。
遮蔽 `sum`、`list` 或 `dict` 等内置项很常见,但可能导致令人困惑的错误。`__builtins__` 模块在需要时提供对原始内置项的访问。选择描述性名称以避免意外遮蔽。
类作用域
类作用域是类属性和方法的特殊命名空间。实例变量通过 `self` 访问。本示例演示了类和实例的作用域。正确的作用域管理对于面向对象的 Python 至关重要。
class MyClass:
# Class variable
class_var = "I'm a class variable"
def __init__(self, instance_var):
# Instance variable
self.instance_var = instance_var
def show_vars(self):
print(f"Class var: {self.class_var}")
print(f"Instance var: {self.instance_var}")
# Local variable
temp = "local value"
print(f"Local var: {temp}")
obj1 = MyClass("Instance 1")
obj2 = MyClass("Instance 2")
obj1.show_vars()
obj2.show_vars()
# Modifying class variable affects all instances
MyClass.class_var = "Modified class var"
obj1.show_vars()
# Additional example: Name resolution in methods
x = "global x"
class Test:
x = "class x"
def method(self):
x = "method x"
print(x) # method x
print(self.x) # class x
print(globals()['x']) # global x
Test().method()
`class_var` 由所有实例共享,而 `instance_var` 对每个对象都是唯一的。`show_vars` 方法演示了如何访问两者,以及其局部变量 `temp`。类变量可以通过类或实例访问。
方法变量遵循 LEGB 规则,实例命名空间(通过 `self` 访问)作为一个额外的作用域。`Test` 类展示了在名称重叠时如何访问不同作用域级别的变量。类作用域在 LEGB 层次结构中介于全局和局部之间。
推导式和 Lambda 中的作用域
推导式和 lambda 有自己不同于常规函数的作用域规则。本示例演示了这些特殊情况。理解这些细微差别可以防止函数式 Python 中的意外行为。
x = 10
# List comprehension has its own scope
squares = [x**2 for x in range(5)]
print(squares) # [0, 1, 4, 9, 16]
print(x) # 10 (unchanged)
# Lambda expressions
y = 20
adder = lambda z: y + z # Lambda can access enclosing scope
print(adder(5)) # 25
# Additional example: Generator expression
multiplier = 3
gen = (x * multiplier for x in range(5))
print(list(gen)) # [0, 3, 6, 9, 12]
# Additional example: Dictionary comprehension
keys = ['a', 'b', 'c']
values = [1, 2, 3]
d = {k: v for k, v in zip(keys, values)} # New scope for k, v
print(d) # {'a': 1, 'b': 2, 'c': 3}
与 Python 2.7 不同,列表推导式不会将其迭代变量(示例中的 `x`)泄漏到外部作用域。Lambda 可以访问外部作用域中的变量,如 `adder` 函数中的 `y` 所示。
生成器表达式在作用域方面与列表推导式类似。字典推导式同样保护其迭代变量。这些函数式结构在允许访问所需的外部变量的同时,保持了清晰的作用域隔离。
作用域最佳实践
优先使用局部变量而不是全局变量,以尽量减少副作用。谨慎使用 `global` 和 `nonlocal`,并在使用时进行文档记录。避免遮蔽内置名称以防止混淆。保持函数小巧且专注,以管理作用域的复杂性。使用描述性名称来减少跨作用域的命名冲突。
资料来源
从以下资源了解更多信息:Python 作用域文档、Real Python LEGB 指南和内置函数。
作者
我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在教授编程方面拥有十多年的经验。
列出所有 Python 教程。