参考
项目 | 描述 |
---|---|
搜索引擎 | Google 、Bing |
Python | 官方文档 |
哔哩哔哩 | 【python】定义好的变量读不出来?详解全局变量和自由变量的使用! |
描述
项目 | 描述 |
---|---|
PyCharm | 2023.1 (Professional Edition) |
Python | 3.10.6 |
变量的三种姿态
全局变量
在 Python 中,全局变量是在程序的 顶层(顶层是指程序中最外层的作用域或命名空间,即全局作用域)
定义的变量,可以在整个程序的任何位置访问。它们不局限于特定的函数或类,而是在整个程序的作用域内可见。
举个栗子
# 全局作用域中定义变量 global_var
global_var = 'Hello World'
def func():
# 尝试在局部作用域中访问全局变量
print(global_var)
func()
执行效果
Hello World
虽然
全局变量可以在程序中随处访问,但过度使用全局变量可能会导致代码难以维护和调试。因此,在设计程序时,减少全局变量的使用,尽量使用函数参数和返回值来传递和获取需要共享的数据。
注:
在 Python 中,内层作用域可以访问外层作用域中的变量,而外层作用域不能直接访问内层作用域中的变量。
自由变量
在 Python
中,自由变量是在函数内部引用但没有在函数内部定义的变量,它们在函数外部的作用域中定义。自由变量可以是全局变量或局部变量。
当在函数内部引用一个变量时,Python 首先在函数的局部作用域中查找该变量。如果没有找到,它会继续在外部作用域中查找。这种查找过程会持续到 找到变量
或达到全局作用域(更准确来说,应该是达到 内置作用域
)。
举个栗子
def external_func():
free_var = 'Hello World'
def internal_func():
# 未定义 free_var 变量,但在该作用域中
# 使用了该变量,故 free_var 为自由变量
print(free_var)
# 调用 internal_func() 函数
internal_func()
external_func()
执行效果
Hello World
遮盖现象
遮盖现象(Name Shadowing)在作用域的层级结构(作用域间的嵌套关系)中发生。当在 较内层(相对于引用自由变量的作用域而言)作用域
中定义一个与 较外层作用域
相同名称的变量时,它会 遮盖
较外层作用域中的同名变量。这样,在较内层作用域中使用该变量时,实际上引用的是较内层作用域中的变量,而不是外层作用域中的变量。对此,请参考如下示例:
# 较外层作用域
# (相对于 internal_func 所在的作用域而言)
free_var = 'Hello World'
def external_func():
# 较内层作用域
# (相对于 internal_func 所在的作用域而言)
free_var = 'Hello China'
def internal_func():
# 如果在当前作用域中定义了 free_var
# 变量,则将使用当前作用域中的 free_var 的值
print(free_var)
internal_func()
执行效果
Hello China
局部变量
在 Python 中,局部变量是在特定作用域可见和可访问的变量。它们是在函数、类或某个特定代码块中定义的变量,只能在其所在的作用域内使用
。
当在函数内部定义一个变量时,它被视为该函数的局部变量,只能在该函数内部使用。当函数执行完毕或退出时,局部变量的 生命周期
也会结束。
举个栗子
def func():
# 定义局部变量 local_var
local_var = 'Hello World'
# 在局部作用域内访问局部变量
print(local_var)
func()
执行效果
Hello World
注:
无法在函数外部访问局部变量。如果你尝试这样做,Python 将抛出异常错误信息。
UnboundLocalError
UnboundLocalError 异常错误
考虑如下代码:
func1()
free_var = 'Hello World'
def func1():
print(free_var)
func1()
在上述代码的执行过程中,当函数被调用后 func1()
后,控制台中将正确输出结果 Hello World
。
func2()
free_var = 'Hello World'
def func2():
print(free_var)
# 尝试为 free_var 变量进行赋值操作
free_var = 'Hello China'
func2()
执行上述代码将产生异常错误信息,其中包含如下内容:
UnboundLocalError: local variable 'free_var' referenced before assignment
UnboundLocalError: local variable 'free_var' referenced before assignment
是 Python 提供的一个异常错误,它表示在局部作用域中引用了一个在使用之前未被赋值的变量。
字节码与 dis.dis()
字节码
在 Python 中,字节码
是一种中间形式的指令集,它由 Python 解释器执行。Python 源代码在运行之前会被编译为字节码,然后由解释器逐条执行字节码指令。
dis.dis()
dis.dis()
函数是 Python 标准库
中的一个函数,用于对给定的函数或方法的字节码进行 反汇编操作
。它可以用来查看函数的字节码指令,帮助我们理解代码的执行过程和优化代码
。
dis.dis(x=None, *, file=None, depth=None)
注:
参数列表中的 *
表示该符号后续的参数必须通过关键字的方式进行传递。
其中:
参数 | 作用 |
---|---|
x | 要进行反汇编的函数对象或代码对象。可以是函数、方法、类、生成器、源代码字符串等对象。 |
file | 将反汇编结果输出至该参数提供的文件对象中。如果未指定文件对象,则默认输出到标准输出对象( sys.stdin )中。 |
depth | 控制反汇编的深度。如果为 None(默认值) ,则对所有嵌套的代码对象进行反汇编。如果为 正整数 ,表示限制反汇编的嵌套深度。 |
尝试对 func1() 进行反汇编操作
from dis import dis
def func1():
print(free_var)
dis(func1)
执行效果
5 0 LOAD_GLOBAL 0 (print)
2 LOAD_GLOBAL 1 (free_var)
4 CALL_FUNCTION 1
6 POP_TOP
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
让我们逐条解释这些字节码指令的含义和作用:
-
LOAD_GLOBAL 0 (print)
这条指令将全局命名空间中的print
对象作为操作数压入栈
(栈是一种存储结构,具有First In Last Out
的特点)中。 -
LOAD_GLOBAL 1 (free_var)
这条指令将全局命名空间中的名称free_var
加载到栈上。它将全局命名空间中的free_var
对象作为操作数压入栈中。 -
CALL_FUNCTION 1
这条指令调用位于栈顶的函数对象,并传递给定数量的参数。在这里,函数对象是print
,参数数量为1
。因此,它将栈顶的1
个参数,即free_var
传递给print
函数并对其进行调用。 -
POP_TOP
这条指令从栈顶弹出一个对象并丢弃。在这里,它将弹出print
函数调用后返回的结果。 -
LOAD_CONST 0 (None)
这条指令将常量None
加载到栈上。它将None
对象作为操作数压入栈中。 -
RETURN_VALUE
这条指令将栈顶的值作为函数的返回值,并结束函数的执行。在这里,它将栈顶的None
对象作为函数的返回值。
尝试对 func2() 进行反汇编操作
from dis import dis
def func1():
print(free_var)
free_var = 'Hello China'
dis(func1)
执行效果
5 0 LOAD_GLOBAL 0 (print)
2 LOAD_FAST 0 (free_var)
4 CALL_FUNCTION 1
6 POP_TOP
6 8 LOAD_CONST 1 ('Hello China')
10 STORE_FAST 0 (free_var)
12 LOAD_CONST 0 (None)
14 RETURN_VALUE
让我们逐条解释这些字节码指令的含义和作用:
对于源代码中第五行代码所对应的字节码有:
-
LOAD_GLOBAL 0 (print)
这条指令将全局命名空间中的print
对象加载到栈上。 -
LOAD_FAST 0 (free_var)
这条指令将函数的局部变量free_var
加载到栈上。 -
CALL_FUNCTION 1
这条指令调用栈顶的函数对象,传递给定数量的参数。在这里,函数对象是print
,参数数量为1
。于是,Python 将栈顶的1
个参数(即free_var
)传递给print
函数进行调用。 -
POP_TOP
这条指令从栈顶弹出一个对象并丢弃。在这里,它将弹出print
函数调用后返回的结果。
对于源代码中第五行代码所对应的字节码有:
-
LOAD_CONST 1 ('Hello China')
这条指令将常量'Hello China'
加载到栈上。 -
STORE_FAST 0 (free_var)
这条指令将栈顶的值(即'Hello China'
)存储到函数的局部变量free_var
中。 -
LOAD_CONST 0 (None)
: 这条指令将常量None
加载到栈上。 -
RETURN_VALUE
这条指令将栈顶的值作为函数的返回值,并结束函数的执行。在这里,它将栈顶的None
对象作为函数的返回值。
注意:这是字节码指令的解释,它展示了代码在底层的执行过程。对于了解
UnboundLocalError 异常错误抛出的原因
比较 func1()
与 func2()
函数的字节码可以发现,func1
加载 free_var
变量时使用的字节码指令为 LOAD_GLOBAL
,而在 func2
加载 free_var
变量时使用的字节指令为 LOAD_FAST
。func1()
尝试从全局变量中加载 free_var
变量,而 func2
则尝试从局部变量中加载 free_var
变量。由于 func2
在局部变量定义前使用了局部变量,由此产生了 UnboundLocalError
异常错误。
产生上述现象的原因是:在默认情况下,Python 仅允许局部作用域访问自由变量,但不支持修改自由变量的值
。
Python 默认不支持修改自由变量所带来的优点:
优点 | 描述 |
---|---|
避免意外的行为 | 禁止函数直接修改自由变量可以减少程序中的不确定性和难以调试的错误。 |
明确作用域边界 | 限制自由变量的修改有助于明确作用域的边界,使代码 更易于理解和维护 。变量的修改仅发生在特定作用域内部,不会影响外部作用域。 |
保护全局状态 | 限制函数对全局变量的直接修改可以保护 全局状态的一致性 ,提高代码的可维护性。全局变量是在全局作用域中定义的,可以被程序的任何部分访问和修改。 |
函数封装和代码重用 | 限制函数对自由变量的修改有助于实现函数的 封装性 和代码的 重用性 ,促进 模块化 的开发方式。 |
如果你需要修改自由变量的值,需要使用 global
或 nonlocal
向 Python 声明这一点。
global 与 nonlocal 关键字
global 关键字
在 Python 中,global
是一个关键字,用于在函数内部访问和修改全局变量。
当在函数内部需要修改(也可以只访问不修改)全局作用域中定义的变量时,需要使用 global
关键字来声明该变量为全局变量。这样可以告诉 Python 解释器在函数内部使用该变量时,不要创建一个同名的局部变量,而是直接使用全局作用域中的变量。对此,请参考如下示例:
free_var = 'Hello World'
def func():
global free_var
# 尝试访问全局变量 free_var
print(free_var)
# 尝试修改全局变量 free_var
free_var = 'Hello China'
func()
# 函数内部对 free_var 的更改将
# 反应到全局作用域中。
print(free_var)
执行效果
Hello World
Hello China
注:
-
global
关键字能够在全局作用域中使用。但在全局作用域中使用global
关键字是没有意义的,并不会导致语法错误的产生。 -
在使用
global
关键字将一个变量声明为全局变量时,该变量可以不存在,但在尝试访问或修改该变量时,若该变量仍未在全局作用域中创建,则 Python 将抛出NameError
异常错误信息。对此,请参考如下示例:def func(): # 此时虽未在全局作用域中定义 free_var 全局变量 # ,但仍可以将 free_var 声明为一个全局变量。 # 在后续访问该变量时将直接从全局作用域中寻找该变量。 global free_var # 若在全局作用域中为定义相关的全局变量, # 尝试访问或修改变量将导致 NameError 异常错误。 # 此时,若执行如下语句将导致 NameError 异常错误。 # print(free_var) func()
也就是说,
global
关键字的作用仅仅是告诉 Python 解释器应该在全局作用域中寻找某一个变量而不会检查该变量是否在全局作用域中定义。 -
如果你使用
global
关键字将一个变量声明为全局变量,那么你不能在global
关键字之前尝试访问或修改该变量。否则,Python 将抛出异常错误。对此,请参考如下示例:free_var = 'Hello World' def func(): # 在 global 关键字前尝试访问或修改其 # 所声明的全局变量都将导致 SyntaxError。 # Python 执行到下一行代码时将抛出 SyntaxError。 print(free_var) global free_var print(free_var) free_var = 'Hello China' func() print(free_var)
nonlocal 关键字
当在一个 嵌套函数(位于一个函数中内部的函数被称为嵌套函数)
中需要访问并修改位于外部函数作用域的变量时,如果该变量不是 全局变量
,而是外部函数的局部变量或自由变量,就需要使用 nonlocal
关键字。通过使用 nonlocal
关键字,我们可以显式地声明一个变量为非局部的,从而允许在 嵌套函数
中修改该变量的值。对此,请参考如下示例:
def external_func():
free_var = 'Hello World'
def internal_func():
nonlocal free_var
# 尝试访问非全局变量的自由变量
print(free_var)
# 尝试修改非全局变量的自由变量
free_var = 'Hello China'
# 执行嵌套函数 internal_func()
internal_func()
# 嵌套函数对非全局变量的自由变量的修改将反映
# 到该变量所处的局部作用域中。
print(free_var)
external_func()
执行效果
Hello World
Hello China
注:
nonlocal
关键字的限制与 global
关键字的限制大致相同,存在如下不同:
-
nonlocal
关键字不能在全局作用域中使用。否则,Python 将抛出SyntaxError
异常错误。 -
在函数定义过程中,
nonlocal
所声明的非全局变量的自由变量必须已经存在。否则,Python 将抛出SyntaxError
异常错误。对此,请参考如下示例:
def external_func():
def internal_func():
# 执行下一行代码时将产生 SyntaxError 异常错误。
# Python 会检查是否存在由 nonlocal
# 关键字声明的非全局变量的自由变量。
nonlocal free_var
internal_func()
external_func()
不允许修改自由变量所指向的值还是其保存的引用?
可变对象与不可变对象
在 Python 中,对象分为 可变对象(Mutable Objects)
和 不可变对象(Immutable Objects)
两种类型。
可变对象
可变对象是指创建后其内部状态可以被修改的对象。常见的可变对象包括列表(list
)、字典(dict
)和集合(set
)等。当对可变对象进行操作时,其 内部的状态可以发生改变
,而对象本身的 身份(Identity,对象的身份即该对象在内存中的存储地址)不会发生改变
。对此,请参考如下示例:
arr = [1, 2, 3, 4, 5, 6]
# 尝试输出可变对象 arr 的身份标识
print(id(arr))
# 对可变对象进行修改
arr.pop()
# 尝试输出可变对象 arr 的身份标识
print(id(arr))
执行效果
由于对象的身份标识是一个内存地址。因此,在不同的计算机中输出的结果存在极大的概率是不相同的。但可以肯定的是,两次输出的身份标识的值是相同的。
2609552406912
2609552406912
不可变对象
不可变对象是指创建后其内部状态不可被修改的对象。常见的不可变对象包括整数(int
)、浮点数(float
)、布尔值(bool
)、字符串(str
)和元组(tuple
)等。当对不可变对象进行操作时,将 重新创建一个对象
,该对象的身份表示与原对象不同。
think = 'Hello World'
# 尝试输出不可变对象 think 的身份标识
print(id(think))
think += '\nHello China'
# 尝试输出不可变对象 think 的身份标识
print(id(think))
执行效果
由于修改不可变对象将创建一个新的对象,因此标识符所关联的 引用(也即内存地址)
也发生了变化。
2629283135216
2629283524688
揭晓答案
当在函数内部修改自由变量时,Python 不允许
直接修改自由变量所保存的引用(内存地址),即 将自由变量绑定到一个新的对象
。如果要修改自由变量所保存的引用,需要使用 global
或 nonlocal
关键字来声明变量,并在其后进行修改操作。
然而,Python 允许
修改自由变量所指向的值,即 修改对象内部的状态而不改变对象本身的引用
。这意味着,对于可变对象(如列表、字典等),可以通过引用修改其内部的值,而无需使用 global
或 nonlocal
关键字。但对于不可变对象(如整数、字符串等),无法修改其值,每次操作都会返回一个新的对象。文章来源:https://www.toymoban.com/news/detail-455243.html
free_var = list('Hello ')
def func():
# 尝试在未使用 global 或 nonlocal 关键字
# 的情况下对可变对象进行修改。
free_var.extend(list('World'))
func()
# 使用空字符串将列表中的所有元素连接为一个字符串
print(''.join(free_var))
执行效果
文章来源地址https://www.toymoban.com/news/detail-455243.html
Hello World
到了这里,关于可远观而不可亵玩焉:Python 中的自由变量的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!