- 变量作用域
- 全局作用域(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 不会进一步在全局作用域中查找,所以会报错。