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 教程。