- 变量作用域
- 全局作用域(Global scope) 和 内置作用域(Built-in scope)
- 局部作用域(Local scope)
- 局部作用域的创建与销毁
- 函数每次调用都会创建新的局部作用域
- 变量查找(Variable lookups)
- `global` 关键字
- 在函数内修改全局变量
- 使用 `global` 关键字修改全局变量
- `nonlocal` 关键字
- 非局部作用域(Nonlocal scope)
- 访问封闭作用域的变量
- `nonlocal` 关键字
变量作用域
参考文章:Python nonlocal、Python Variable Scopes
当你将一个对象赋值给一个变量时,该 变量会在内存中引用该对象。这意味着 变量被绑定到该对象,可以 通过变量名访问该对象,但是要注意 变量名 及其 绑定(变量名和对象)仅存在于代码的特定部分。
Python 将这些 绑定 存储在 命名空间(namespace) 中。每个作用域都有自己的命名空间。
全局作用域(Global scope) 和 内置作用域(Built-in scope)
-
全局作用域(
global scope)本质上就是 模块作用域(module scope),它仅限于 单个 Python 源代码文件。 -
内置作用域(
built-in scope)是一个 跨所有模块 的真正全局作用域,它提供全局可用的对象,例如print、len、None、True和False等。 -
在内部,全局作用域是嵌套在内置作用域之中的。

如果你在某个作用域中 访问一个变量,而 Python 在该作用域的命名空间中找不到 它,它会 继续在外层作用域的命名空间中查找。
假设你在一个名为 app.py 的模块中有以下语句:
print('Hello')
-
在
app.py模块中,Python 会首先在模块作用域(app.py)中查找print函数。 -
由于 Python 在
app.py的模块作用域中找不到print函数的定义,它会向上查找外层作用域,即内置作用域(built-in scope),并在那里寻找print函数。 -
在这种情况下,Python 能够在内置作用域中找到
print函数并正确执行。

局部作用域(Local scope)
当你创建一个函数时,可以在函数内定义参数和变量。例如:
def increment(counter, by=1):result = counter + byreturn result
当执行代码时,Python 经过两个阶段:编译(compilation) 和 执行(execution)。
- 编译阶段:Python 将
increment函数添加到全局作用域(global scope)。 - 执行阶段:Python 识别
increment()函数中的counter、by和result变量属于局部作用域(local scope),但不会立即创建这些变量,直到函数被调用时才会分配内存。
局部作用域的创建与销毁
每次调用函数时,Python 都会创建一个新的作用域,并将函数内部定义的变量分配到该作用域。这被称为 函数局部作用域(function local scope),简称 局部作用域。
例如,当你调用 increment(10,2) 时:
increment(10,2)
Python 进行以下操作:
- 创建一个新的局部作用域,用于存储
increment()调用时的变量。 - 创建局部变量
counter、by和result,并将它们分别绑定到10、2和12。 - 执行函数,返回
result值。 - 函数执行结束后,Python 会删除这个局部作用域,所有局部变量
counter、by和result都超出作用域(out of scope),无法从函数外部访问。
如果你尝试在 increment() 之外访问这些变量,会导致错误。
函数每次调用都会创建新的局部作用域
如果再次调用 increment(100,3):
increment(100,3)
Python 不会复用上一次的局部作用域,而是:
- 创建新的局部作用域。
- 重新创建
counter、by和result变量,并分别绑定到100、3和103。 - 执行函数,返回
result值。 - 函数执行结束后,Python 再次删除这个局部作用域。
每次函数调用,都会有独立的局部作用域,互不影响。
变量查找(Variable lookups)
在 Python 中,作用域是嵌套的(nested)。例如:局部作用域(local scope) 嵌套在 模块作用域(module scope) 内,而 模块作用域 又嵌套在 内置作用域(built-in scope) 内。

当你访问一个变量所绑定的对象时,Python 按照以下顺序查找该对象:
- 首先 在 当前的局部作用域(local scope) 中查找。
- 如果找不到,Python 会沿着 嵌套的外层作用域 逐级向上查找,直到找到该对象,或者到达最外层的 内置作用域(built-in scope)。
如果 Python 在 所有作用域中都找不到该变量,就会抛出 NameError 异常。
global 关键字
当你在函数内部 获取 一个 全局变量 的值时,Python 会 自动 在局部作用域(local scope)查找变量,如果找不到,则继续向上搜索所有 封闭作用域(enclosing scopes)中的变量。例如:
counter = 10def current():print(counter) # 访问全局变量 countercurrent()
在这个例子中,
- 当
current()函数运行时,Python 先在局部作用域中查找counter变量。 - 由于
counter不在局部作用域内,Python 继续在全局作用域中查找,并成功找到counter = 10,所以输出10。
在函数内修改全局变量
然而,如果 在函数内部赋值 给全局变量,Python 默认会在 局部作用域 创建一个 同名变量,而不是修改全局变量。例如:
counter = 10def reset():counter = 0 # 这里创建了一个局部变量 counterprint(counter) # 输出 0reset()
print(counter) # 输出 10(全局变量未被修改)
-
在编译阶段(compile time),Python 认为
reset()函数内部的counter是局部变量,所以它在reset()的作用域中创建了一个新的counter变量。 -
当
reset()运行时,counter = 0仅修改了局部作用域的counter,不会影响全局作用域的counter。 因此,在reset()运行后,全局counter仍然保持10。
在这个例子中,局部作用域的 counter 遮蔽了全局作用域的 counter。
使用 global 关键字修改全局变量
如果你希望在函数内部修改全局变量,需要使用 global 关键字。例如:
counter = 10def reset():global counter # 告诉 Python 这里的 counter 指的是全局变量counter = 0print(counter) # 输出 0reset()
print(counter) # 输出 0
在这个例子中,global counter 语句告诉 Python:counter 变量属于全局作用域,而不是局部作用域。 这样,counter = 0 便会直接修改全局变量 counter,所以 reset() 运行后,全局 counter 也被更新为 0。
注意:尽管
global关键字可以让函数内部修改全局变量,但 这不是一个好的编程实践,更好的做法是:尽量使用函数参数和返回值,而不是直接修改全局变量。
nonlocal 关键字
Python 中的 nonlocal 关键字,
nonlocal用于声明 嵌套函数 内部的变量 来自外层函数,而 不是全局作用域。- 它 允许修改外层函数作用域中的变量,而 不仅仅是访问 它们。
非局部作用域(Nonlocal scope)
在 Python 中,你可以在一个函数内部定义另一个函数。例如:
def outer():print('outer function')def inner():print('inner function')inner()outer()# outer function
# inner function
在上面的例子中,定义了一个名为 outer 的函数。 在 outer 函数内部,又定义了 inner 函数,并在 outer 函数内部调用 inner。通常,称 inner 嵌套(nested)在 outer 之中。
在实际应用中,当你不希望某个函数是全局可用时,就可以使用嵌套函数。
outer 和 inner 函数都能访问:
- 全局作用域(
global scope) - 内置作用域(
built-in scope) - 各自的局部作用域(
local scope)
此外,inner 还可以访问 outer 的作用域,这被称为 封闭作用域(enclosing scope)。
从 inner() 函数的角度来看,它的封闭作用域 既不是局部作用域(local scope),也不是全局作用域(global scope), Python 将其称为 nonlocal 作用域(非局部作用域)。
访问封闭作用域的变量
修改 outer 和 inner 函数,给 outer 添加一个变量 message:
def outer():message = 'outer function' # 定义一个局部变量 messageprint(message)def inner():print(message) # inner() 访问 outer() 的变量 messageinner()outer()# outer function
# outer function
当 outer() 执行时:
- Python 创建
inner()函数,但不会立即执行它。 outer()先打印message的值,即"outer function"。outer()调用inner(),开始执行inner()内部的代码。- 在
inner()中,Python 需要查找变量message:- 先查找
inner()的局部作用域,但inner()里没有message。 - 然后查找封闭作用域(outer 的作用域),找到了
message = 'outer function'。 - 成功获取
message的值,并打印 “outer function”。
- 先查找
这种 变量查找规则 称为 LEGB 规则(Local → Enclosing → Global → Built-in)。

看下面的示例:
message = 'global scope'def outer():def inner():print(message) # 在 inner 函数内部访问 message 变量inner()outer()# global scope
在这个示例中,
- Python 先在
inner函数的 局部作用域(local scope) 中查找message变量。 - 由于在
inner函数的作用域内找不到该变量,Python 继续在封闭作用域(enclosing scope)(即outer函数的作用域)中查找。 - 但
outer函数的作用域中也没有message变量,于是 Python 继续向上查找,最终在 全局作用域(global scope) 中找到了message变量,并成功打印"global scope"。

nonlocal 关键字
要 在局部作用域中修改非局部作用域的变量,可以使用 nonlocal 关键字。例如:
def outer():message = 'outer scope'print(message)def inner():nonlocal messagemessage = 'inner scope'print(message)inner()print(message)outer()# outer scope
# inner scope
# inner scope
在这个示例中,使用 nonlocal 关键字显式地告诉 Python,我们正在修改一个非局部变量。
当你对一个变量使用 nonlocal 关键字时,Python 会 在封闭的局部作用域链中查找该变量,直到第一次遇到这个变量名为止。
更重要的是,Python 不会在全局作用域中查找这个变量。
message = 'outer scope'def outer():print(message)def inner():nonlocal messagemessage = 'inner scope'print(message)inner()print(message)outer()
如果运行这段代码,会得到以下错误:
SyntaxError: no binding for nonlocal 'message' found
-
在
inner函数内部,对变量message使用了nonlocal关键字。 -
因此,Python 会在封闭作用域中查找
message变量,也就是outer函数的作用域。 -
由于
outer函数的作用域中并没有message变量,并且 Python 不会进一步在全局作用域中查找,所以会报错。

