函数中变量的作用域和生命周期
作用域(scope)规定了变量的可访问范围和其生命周期
生命周期:变量的生命周期取决于其所在的作用域。当程序进入一个作用域时,其中定义的变量被创建;当程序离开该作用域时,这些变量被销毁,内存空间被释放,这也是外部无法访问函数内的变量的原因
Python 中有四种作用域:
- 全局作用域
- 局部作用域
- 嵌套作用域
- 内置作用域
相应的根据变量所属的作用域我们可以将变量分为:
- 全局变量
- 局部变量
- 非局部变量
- 内建变量
全局作用域(Global Scope)
全局作用域是在整个程序中都可访问的作用域,在函数外部定义的变量属于全局作用域
示例:
x = 10 # 全局变量
def my_function():
print(x) # 可以访问全局变量x
my_function() # 输出: 10
print(x) # 输出: 10
局部作用域(Local Scope)
局部作用域是在函数内部定义的变量的作用域,局部作用域仅限于函数内部
示例:
def my_function():
y = 5 # 局部变量
print(y) # 可以访问局部变量y
my_function() # 输出: 5
print(y) # 报错,无法访问局部变量y
嵌套作用域(Enclosing Scope)
嵌套作用域是指在一个函数内部定义的函数中的变量的作用域,嵌套作用域的变量在外部函数执行时被创建,在外部函数执行结束时被销毁
示例:
def outer_function():
x = 10 # 外部函数的局部变量
def inner_function():
y = 5 # 嵌套函数的局部变量
print(x + y) # 可以访问外部函数的变量
inner_function() # 输出: 15
outer_function()
内置作用域(Built-in Scope)
内置作用域是指 Python 解释器中预定义的命名空间,包含了一些内置的函数和对象。这些内置函数和对象可以在任何地方直接使用,无需导入任何模块
示例:
print(len("Hello")) # 输出: 5,len() 是内置函数
作用域总结
- 全局作用域:整个程序中都可访问的作用域
- 局部作用域:在函数内部定义的变量的作用域
- 嵌套作用域:在一个函数内部定义的函数中的变量的作用域
- 内置作用域: Python 解释器中预定义的命名空间
上面我们提到,一个变量的生命周期由其所在的作用域决定
- 全局作用域的变量在程序开始执行时被创建,在程序结束时被销毁
- 局部作用域的变量在函数执行时被创建,在函数执行结束时被销毁
- 嵌套作用域的变量在外部函数执行时被创建,在外部函数执行结束时被销毁
- 内置作用域在解释器启动时自动开始加载,直至解释器关闭
全局变量(Global Variables)
在函数外部(全局作用域)定义的变量被称为全局变量,可以整个程序的任意地方访问。全局变量的作用域从定义开始,直至程序结束销毁
示例:
x = 10 # 全局变量
def my_function():
print(x) # 可以访问全局变量x
my_function() # 调用定义的函数输出: 10
print(x) # 输出: 10
global关键字声明
一般来说,函数内可以直接访问全局变量,但是无法修改,因为在函数内部是一个局部作用域,程序会默认在其内部是是一个新的局部变量,而不是外部声明的全局变量,此时如果我们要在函数内部对全局变量进行修改,我们要在变量前面使用global
关键字进行声明告诉程序这是一个在函数内部使用的全局变量
x = 10 # 全局变量
def my_function():
global x # 使用 global 关键字声明 x 是全局变量
x = 20 # 修改全局变量 x 的值
print(x) # 访问全局变量
my_function() # 输出:20
print(x) # 输出:20
局部变量(Local Variables)
在函数内部(局部作用域)定义的变量被称为局部变量,局部变量的生命周期仅限于函数的执行过程**在其所属函数执行时创建,在函数执行结束后销毁
** 因此只能在其所属函数内部访问,函数外无法访问
示例:
def my_function():
y = 5 # 局部变量
print(y) # 可以访问局部变量y
my_function() # 调用定义的函数输出: 5
print(y) # 报错,无法访问局部变量y(全局无法访问局部)
注意:由于查找顺序的问题,如果在函数内部和外部存在同名的变量,调用函数时函数内部的变量会覆盖外部的变量(这里的覆盖仅在函数内部使用时,对于全局来说没有影响)
示例:
z = 15 # 全局变量
def my_function():
z = 20 # 局部变量,覆盖了全局变量z
print(z) # 可以访问局部变量z
my_function() # 调用定义的函数输出: 20
print(z) # 输出: 15,全局变量z没有被修改
而在嵌套函数中,如果有与外部函数同名的变量,那么在嵌套函数中对该变量的赋值会创建一个新的局部变量,而不会修改外部函数的变量
示例:
def outer_function():
x = 10 # 外部函数的局部变量
def inner_function():
x = 20 # 内部函数的局部变量,不会修改外部函数的变量x
print("Inner function:", x)
inner_function()
print("Outer function:", x)
outer_function()
# 输出 :
Inner function: 20
Outer function: 10
非局部变量(Nonlocal Variables)
非局部变量是在嵌套函数中定义的变量,非局部变量使用 nonlocal
关键字声明,将外部函数的局部变量在内部函数声明成非局部变量,使内部函数中可以修改外部函数的变量
def outer_function():
x = 10 # 外部函数的局部变量
def inner_function():
nonlocal x # 将x声明为非局部变量
x = 20 # 修改外部函数的变量x的值
inner_function()
print(x) # 输出: 20
outer_function()
内建变量(Built-in Variables)
内建变量是 Python 内置的变量,可以直接在程序中使用,无需声明或者定义。例如 True
、False
、None
等
变量的查找顺序(LEGB)
上面我们提到了,如果函数内部有与外部相同的变量名,那么调用函数时,局部变量会覆盖全局变量,这涉及到变量查找顺序的问题
在 Python 中,当我们使用一个变量时,解释器会按照以下顺序去查找这个变量的值
图片来源于网络,侵权联系删除
1. 局部作用域(Local Scope)
首先,解释器会在当前的函数或者代码块中查找变量。如果变量在当前的作用域中定义了,那么解释器会直接使用该变量的值。
2. 嵌套作用域(Enclosing Scope)
如果变量在当前的作用域中没有定义,解释器会继续在上一级的作用域中查找变量,直到找到为止。这种作用域的关系称为嵌套作用域,即内部作用域可以访问外部作用域中的变量。
3. 全局作用域(Global Scope)
如果变量在所有的局部作用域和嵌套作用域中都没有定义,解释器会在全局作用域中查找变量。全局作用域是在整个程序范围内可见的,通常在函数外部定义的变量属于全局作用域。
4. 内置作用域(Built-in Scope)
如果变量在全局作用域中也没有定义,解释器会在内置作用域中查找变量。内置作用域包含了 Python 内置的函数和变量,例如 print() 和 len() 等。
如果在所有的作用域中都没有找到变量的定义,解释器会返回一个 NameError
的异常。