函数参数,按值传递 还是 按引用传递?
单纯从行为上看,Python的函数参数语义“既支持按值调用也支持按引用调用”,不同情况下,其表现不同
更准确的说,Python函数的调用语义是:按对象引用调用语义
Python中的变量并不是与其他编程语言中的变量不同,这些变量是对象引用
可以把变量中存储的值认为是值的内存地址,而不是它真正的值
传递参数时,会把这个内存地址传入函数(而非传入实际的值)
传递参数时,发生了什么
实际上,传递参数时解释器会查看对象引用(内存地址)指示的那个值的类型:
-
如果其为可变的,就会应用按引用传递;(如列表、字典、集合)
在函数中,修改可变类型的参数,其本身会改变 -
如果是不可变的,则会应用按值传递(如字符串、整数、元组)
在函数中,修改不可变类型的参数,其本身不改变 -
另外要注意,传入的参数是否真正改变,不是看函数参数的变量内容是否改变,而应该看传入的原始对象的对象引用(内存地址)指向的值是否改变
(详见下面的例子)
利用这个细节,可以用赋值语句让可变型参数表现得像按值传递
(见文末的例子)
对于不可变类型的参数,按值传递
这意味着在函数中,修改不可变类型的参数,其本身不改变
def double(arg):
print('Before: ', arg)
arg = arg * 2
print('After: ', arg)
================= RESTART: C:\Users\13272\Desktop\mystery.py =================
>>> num=2
>>> double(num)
Before: 2
After: 4
>>> num
2
>>> saying='Hello'
>>> double(saying)
Before: Hello
After: HelloHello
>>> saying
'Hello'
>>> nums=[1,2,3]
>>> double(nums)
Before: [1, 2, 3]
After: [1, 2, 3, 1, 2, 3]
>>> nums
[1, 2, 3]
最后一个例子似乎不太对?赋值语句“阻止了”按引用传递
最后一个例子中,传入一个列表(可变类型),然后调用函数后,列表并未改变
这是bug吗?这个列表怎么按值传递了?
其实并不是。
是否修改了传入的参数:不是看最开始用于保存参数的arg变量是否改变(Python中变量存储的都是对象引用而不是实际的值),而是应该看(传入参数时)原始参数的对象引用(内存地址)指向的值是否改变
在这里,函数中并未改变传入的列表nums
关键问题在于赋值语句
arg = arg * 2
- 这句话是赋值语句
- =右边:对原来的列表
*2
操作,得到另一个新的列表对象; - =左边:将新对象的对象引用赋给等号左边的变量arg
- 赋值语句虽然覆盖变量arg原先存储的对象引用,但原始的“老”对象引用仍存在,且其指向的值未改变,所以解释器仍看到原来的列表
如果改为
arg[:] = arg * 2
即可实现原地修改,原来的列表也会被改变
下面的例子中,使用列表的append(),则成功改变了“传入的原始列表”的对象引用所指向的值,从而体现“可变参数按引用传递”(传入的参数改变,其本身也改变)
对于可变类型的参数,按引用传递
这意味着在函数中,修改可变类型的参数,其本身也会改变
def change(arg):
print('Before: ', arg)
arg.append('More data')#修改了原始对象引用指向的值
print('After: ', arg)
================= RESTART: C:\Users\13272\Desktop\mystery.py =================
>>> nums=[1,2,3]
>>> change(nums)
Before: [1, 2, 3]
After: [1, 2, 3, 'More data']
>>> nums
[1, 2, 3, 'More data']
利用赋值语句,防止按引用传递
根据上面的讨论,我们知道
- 如果让函数存储参数的变量(通过赋值语句)不再指向原始对象,就能防止改变传入函数的可变参数
- 从而不再“按引用传递”,而是表现得像“按值传递”
例如,向上一个程序中增加arg = arg.copy()
赋值语句
def change(arg):
print('Before: ', arg)
arg=arg.copy()#注意这个赋值语句,让arg指向了一个新对象(而不是原始对象)
arg.append('More data')
print('After: ', arg)
================= RESTART: C:\Users\13272\Desktop\mystery.py =================
>>> nums=[1,2,3]
>>> change(nums)
Before: [1, 2, 3]
After: [1, 2, 3, 'More data']
>>> nums
[1, 2, 3]
#就像“按值传递”一样,不改变其本身