在Python中,“按值传递”或“按引用传递”的概念并不像在C或C++等语言中那样准确适用。
这是许多,如果不是所有,混淆的根源。
例如,在下面的代码片段中,一个函数接收一个变量并改变它的值:
def test_func(val):
val = val + ' 2024'
print(val)
author = 'sora gui'
test_func(author)
# sora gui 2024
print(author)
# sora gui
我们知道,按引用传递意味着函数接收传递的参数的变量引用,因此修改参数也会改变原始变量。
根据上面的结果,即使test_func()
改变了接收的变量,author
的原始值也没有改变。
因此,看起来Python是按值传递,这意味着对象的副本被传递给函数。
然而,如果真的是按值传递,以下的结果将是令人惊讶的:
def test_func(val):
print(id(val))
author = 'sora gui'
print(id(author))
# 4336030448
test_func(author)
# 4336030448
接收的变量与原始变量的id完全相同!
我们知道内置的id()
函数提供了对象存储的内存地址。如果val
的id与author
相同,说val
是author
的副本是不合理的。显然,它只是对相同值的另一个引用。
那么现在我们该如何解释这些结果呢?
让我们回到Python的基础:
Python中的一切都是对象,而赋值操作只是将一个名称绑定到一个对象.
因此,在我们的例子中,val
只是author
之外的另一个名称,它被绑定到字符串"sora gui"。
这两个名称在内存中有相同的地址,因为它们都绑定到相同的对象。
然而,如果我们在函数内部改变字符串,因为在Python中字符串是不可变的,而不是改变原始字符串,将会创建一个新的字符串对象,并且名称val
将与其绑定。
这种机制既不是按值传递也不是按引用传递。
这是“按共享传递”。
这就是为什么理解Python变量的可变性很重要的原因。
让我们稍微改变之前的例子,看看结果:
def test_func(val):
val.append('gui')
print(val)
print(id(val))
author = ['sora']
print(id(author))
# 4305358976
test_func(author)
# ['sora', 'gui']
# 4305358976
print(author)
# ['sora', 'gui']
我们将author
变量从字符串转换为列表。现在看起来Python现在是按引用传递了。因为原始列表也被修改了。
这是否与先前的例子矛盾呢?
如果您完全理解按共享传递,您会认为结果是正常的。
val
无论是字符串还是列表,都与author
具有相同的id。因为它们只是相同值的两个不同名称。
关键的一点是,Python中的字符串是不可变的,但列表是可变的。
因此,在函数内部修改字符串的值将创建一个新的字符串对象,但直接在原始列表上修改列表,不会创建新的列表。
误解Python变量的可变性会在生产系统中引起难以发现的错误。
总结
相信通过今天的学习,你明白了Python中是通过引用传递还是通过值传递。