Last Updated on
参数通过赋值传递。其背后的理由是双重的:
- 传入的参数实际上是对对象的引用(但引用是按值传递的)
- 有些数据类型是可变的,但有些不是
所以:
- 如果将可变对象传递给方法,则该方法将获得对该对象的引用,并且可以对其进行突变,但是如果您将该引用重新绑定到该方法中,则外部作用域对此一无所知完成后,外部参考仍将指向原始对象。
- 如果将不可变对象传递给方法,则仍然无法重新绑定外部引用,甚至无法使对象发生突变。
为了更加清楚,让我们看一下下面的例子:
a = 'old'
def fun(a):
print("1:", a, id(a))
a = 'new'
print("2:", a, id(a))
print("before:", a, id(a))
fun(a)
print("after:", a, id(a))
# 输出结果:
before: old 4403148984
1: old 4403148984
2: new 4403148928
after: old 4403148984
可以发现,在函数中,当传入的a为字符串时,在函数中的变化,对外部的变量不影响的。在执行完a = 'new' 之后,a
引用中保存的值,即内存地址发生变化,由原来 'old' 对象的所在的地址变成了 ' new' 这个实体对象的内存地址。
a = ['old']
def fun(a):
print("1:", a, id(a))
a.append('new')
print("2:", a, id(a))
print("before:", a, id(a))
fun(a)
print("after:", a, id(a))
# 输出结果:
before: ['old'] 4346265992
1: ['old'] 4346265992
2: ['old', 'new'] 4346265992
after: ['old', 'new'] 4346265992
但当出入的参数为列表时,函数中对其的修改,会改变外部变量。a引用的内存值也没有发生改变。
我们再看一个例子:
a = ['old']
def fun(a):
print("1:", a, id(a))
a = ['new']
print("2:", a, id(a))
print("before:", a, id(a))
fun(a)
print("after:", a, id(a))
# 输出结果:
before: ['old'] 4463616392
1: ['old'] 4463616392
2: ['new'] 4463616264
after: ['old'] 4463616392
当我们使用可变对象列表的时候,对象的引用是传递到了函数中,但是当我们在方法中重新将该引用绑定到新的值,则外部作用域对此依然一无所知,外部参考仍将指向原始对象。
所以,现在好理解了吧,函数穿参时会将参数的引用传递到函数中,但是如果在函数中将参数重新绑定新的引用,此不会影响到外部作用域,但是如果只是对传入的参数进行修改突变,这需要传入的参数为可变参数,那么参数的修改将会影响到外部作用域。
在python中,strings, tuples, 和numbers是不可更改的对象,而 list, dict, set 等则是可以修改的对象。
那么在实际写代码中,就需要注意函数的参数传递与操作是否会影响到外部作用域,导致一些不知名的错误,那么我们一般怎么解决这个问题呢:
1,在操作不可变对象时,使用return返回新值
def return_a_whole_new_string(the_string):
new_string = something_to_do_with_the_old_string(the_string)
return new_string
# then you could call it like
my_string = return_a_whole_new_string(my_string)
2,在操作可变对象时,若不想影响到外部作用域时,在函数中可以使用深copy复制一个新的内存地址的对象,再对此对象进行操作修改,以保证外部作用域的对象不会发生改变。
import copy
a = ['old']
def change_list(a):
b = copy.deepcopy(a)
b.append('new')
return b
至于深copy,浅copy等的区别与理解,详情请看我的另一篇博文: