跳至内容
Python

详解 Python 变量作用域与 LEGB 规则

了解变量作用域的要点并熟悉“LEGB”规则。您还将看到 global 和 nonlocal 关键字的实际用法。
Sejal Jaiswal's photo
Sejal Jaiswal
已更新 · 8分钟 阅读

使用 AI 探索

ChatGPTClaudePerplexity

创建您的免费账户

实践并学习路径你的进步。

使用 Google 继续
继续操作即表示您接受我们的《使用条款》和《隐私政策》,并同意您的数据存储在美国。

刚写了几行代码就看到 “name is not defined(名称未定义)” 或 “local variable referenced before assignment(在赋值前引用了局部变量)” 真让人挫败。代码看起来没问题,但 Python 并不认同。根因几乎总是作用域:某个名字在何处可见,以及此刻它指向哪个对象。

一旦理解了 Python 的名称查找——通常概括为 LEGB 规则——这些错误就会变得可预测且易于修复。本文将以循序渐进的方式讲解 Python 的变量与作用域。

什么是变量?

变量是绑定到对象的名字。在 Python 中,您使用赋值运算符 = 创建绑定。名称本身无类型;它们可以指向任何对象(字符串、整数、列表、函数等)。

customer_name = "Ava Torres"
order_count = 1
total_cents = 1 + 2 * 300  # evaluated first, then bound

命名变量时遵循以下规则与约定,以避免语法错误和隐蔽的 bug。

  • 仅使用字母、数字和下划线;名称不能以数字开头。
  • 不要使用 True、for 或 class 等关键字作为名称。
  • 避免遮蔽内置名,如 list、dict、sum 或 max。
  • 为可读性优先使用 snake_case(PEP 8)。

这些示例演示了不应做的事情以及 Python 抛出的错误:

first string value = "First string"  # spaces not allowed
# SyntaxError: invalid syntax

1st_value = 10  # cannot start with a digit
# SyntaxError: invalid decimal literal

True = "yes"  # cannot assign to a keyword
# SyntaxError: cannot assign to True

Python 作用域如何工作(LEGB)

作用域是名称可见的代码区域。当您使用一个名称时,Python 按以下顺序查找(LEGB):

  • Local:当前函数的作用域。
  • Enclosing:任意外层函数的作用域(用于嵌套函数)。
  • Global:模块的顶层(模块命名空间)。
  • Built-in:Python 在 builtins 中定义的名称(例如 len、print)。

名称存在于命名空间中(可将其视为将名称映射到对象的字典)。作用域关乎 Python 在代码中的某一点为解析名称会查阅哪些命名空间。

局部作用域

在函数内赋值的名称,除非另有声明,都是该函数的局部名称。它们在函数外不可见。

def show_order_id():
    order_id = 42
    print("inside function:", order_id)

show_order_id()
print("outside function:", order_id)  # NameError

Python 在编译时决定作用域。如果一个函数在其主体的任意位置对某个名称进行了赋值,那么该函数内对该名称的所有使用都会被视为局部——这会导致常见的 UnboundLocalError,当您在赋值前读取它时就会发生:

discount_rate = 0.10  # module-level (global)

def price_with_discount(amount_cents):
    print("configured discount:", discount_rate)  # looks local because of the assignment below
    discount_rate = 0.20  # assignment makes 'discount_rate' local in this function
    return int(amount_cents * (1 - discount_rate))

# UnboundLocalError: cannot access local variable 'discount_rate' where it is not associated with a value

若要在函数中使用模块级名称,要么避免对其赋值,要么将其标记为 global(下文介绍)。

外层作用域(闭包)

嵌套函数可以看到其直接外层函数中的名称。若要重新绑定这样的名称(而不只是读取),请声明为 nonlocal。

def make_step_counter():
    count = 0  # enclosing scope for 'increment'

    def increment():
        nonlocal count  # rebind the 'count' in the nearest enclosing function
        count += 1
        return count

    return increment

step = make_step_counter()
print(step())  # 1
print(step())  # 2

没有 nonlocal 时,在 increment() 中对 count 赋值会创建一个新的局部名称,从而让外层的 count 保持不变。

全局作用域

在模块顶层赋值的名称属于该模块的全局命名空间。任何函数都可以读取它们。若要在函数内部为模块级名称赋值,请将其声明为 global。

greeting = "Hello"

def greet_city(city_name):
    print(greeting, city_name)  # reads global

def set_greeting(new_greeting):
    global greeting
    greeting = new_greeting     # rebinds global

greet_city("Nairobi")  # Hello Nairobi
set_greeting("Hi")
greet_city("Nairobi")  # Hi Nairobi

谨慎使用 global。更可取的做法是通过参数传递值并返回结果,以保持代码的可测试性与可预测性。

内置作用域(以及关于关键字的一点说明)

内置作用域包含 len、print、Exception 等名称。避免遮蔽它们,否则您将在该作用域中失去对相应内置的访问。

list = [1, 2, 3]     # shadows the built-in 'list' constructor
list("abc")          # TypeError: 'list' object is not callable
del list             # fix by deleting the shadowing name

关键字(例如 if、for、def)是 Python 语法的一部分,而非内置命名空间,永远不能作为标识符使用。

代码块、循环与推导式

Python 的代码块语句与推导式具有特定的作用域行为,这常让从其他语言转来的开发者感到意外。

if/for/while/with 没有块级作用域

这些代码块中的赋值会影响其包含作用域(函数或模块)。循环变量在循环结束后也依然存在。

if True:
    status = "ready"
print(status)  # "ready"

for i in range(3):
    pass
print(i)  # 2 (the last value from the loop)

推导式会隔离其迭代变量

列表、字典与集合推导式会为循环变量创建自己的局部作用域。迭代变量不会泄漏到外层作用域。

numbers = [1, 2, 3]
[x for x in numbers]
print("x" in globals() or "x" in locals())  # False

Python 3.12(PEP 709)为提速而内联了推导式,同时保留了这种隔离;您依然可以获得清晰且不泄漏的循环变量,并享受更快的执行。

使用 global 关键字

当您需要在函数中重新绑定一个模块级变量时,将该名称声明为 global。将声明放在函数顶部附近,以便清楚地表明您要修改哪个变量。

tax_rate = 0.08

def configure_tax(rate):
    global tax_rate
    tax_rate = float(rate)

def total_with_tax(cents):
    return int(cents * (1 + tax_rate))

configure_tax(0.10)
print(total_with_tax(1000))  # 1100

使用 nonlocal 关键字

使用 nonlocal 来重新绑定最近外层函数作用域中的名称。这在维护状态的闭包中很常见。

def make_accumulator(start=0):
    total = start
    def add(amount):
        nonlocal total
        total += amount
        return total
    return add

acc = make_accumulator()
print(acc(5))   # 5
print(acc(10))  # 15

对未在外层函数中定义的名称使用 nonlocal 会导致 SyntaxError。如果该名称是全局的,请改用 global。

locals() 与 globals() 在 2025

这些函数适用于检查与调试,而非用于更新变量。从 Python 3.13(PEP 667)开始,每次在函数中调用 locals() 都会返回一个独立的快照。编辑该快照不会改变真实的局部变量。我在 Python 3.13 上运行以下代码片段验证了这一行为:

def probe():
    project = "alpha"
    snap1 = locals()
    snap1["project"] = "beta"  # edits the snapshot only
    observed = project         # still "alpha"
    snap2 = locals()           # new snapshot
    return snap1["project"], observed, snap2["project"]

print(probe())  # ('beta', 'alpha', 'alpha')

以类似方式使用 globals() 来访问模块命名空间。在编写应用代码时,优先使用显式的参数与返回值,而非动态查找。

常见陷阱与修复方法

以下失误占了我在实践中见到的大多数作用域相关错误。

  • UnboundLocalError 源于在函数内赋值: 只要在函数内任意位置对名称赋值,Python 就将其视为局部名。要么将读取放到赋值之后、要么改名、要么按需添加 global/nonlocal。
  • 误以为存在块级作用域: 在 if/for/while/with 中赋值的名称会在包含作用域中继续存在。使用更小的辅助函数来收紧作用域。
  • 遮蔽内置名: 避免使用 list、dict、sum、id 或 min 等名称。选择更具描述性的名称,如 customer_list 或 min_allowed。
  • 在闭包中忘记使用 nonlocal: 如果您打算更新外层函数的变量,请将其声明为 nonlocal。否则会创建一个新的局部变量,外层值不会改变。
  • 混淆关键字与内置名: 关键字(语法)永远不能用作名称;内置名虽然可以被遮蔽,但不应如此做。

让作用域更简单的最佳实践

这些习惯能让代码更易读、易测、易调试。

  • 倾向于小而聚焦的函数。在尽可能小的作用域中定义变量。
  • 通过参数与返回值传入传出数据。尽量减少模块级状态。
  • 有意识地使用闭包。记录嵌套函数捕获了哪些外部名称,确需重新绑定时使用 nonlocal。
  • 选用具描述性且不冲突的名称。以单下划线开头(例如 _cache)表示内部使用。
  • 将 locals()/globals() 视为只读诊断工具,而非配置机制。

结语

Python 通过按 Local → Enclosing → Global → Built-in 的顺序解析名称。理解这一顺序——以及何时使用 global 或 nonlocal——能消除 NameError 与 UnboundLocalError 等常见错误。让变量处于尽可能小的作用域,避免遮蔽内置名,并有意识地使用闭包。掌握这一心智模型后,您的函数将行为可预测,作用域不再带来意外。


Sejal Jaiswal's photo
Author
Sejal Jaiswal
LinkedIn

我曾在多个行业工作,身兼数职:软件开发工程师、机器学习研究员、数据科学家、产品经理。但归根结底,我是一名热爱学习并乐于分享知识的程序员!

主题
Python

进一步了解 Python

课程

Python 入门

4 小时
7.1M
只需 4 小时就能掌握 Python 数据分析基础知识。 本在线课程将介绍 Python 接口并探索常用包。
查看详情开始课程

创建您的免费账户

实践并学习路径你的进步。

使用 Google 继续
继续操作即表示您接受我们的《使用条款》和《隐私政策》,并同意您的数据存储在美国。
查看更多