函数的传参是引用
Python唯一支持的参数传递模式的共享传参(call by sharing)。多数面向对象语言都采用这个模式。
共享传参是指函数中各个形式参数获得的是实参的引用的副本。也就是说,函数内部的形参是实参的别名。
这样的结果就是,函数可能会修改作为参数传入的可变对象,但不会改变标识。
千万不用使用可变类型作为参数的默认值!
一个简单的Bus类,使用可变类型作为参数,结果变成了幽灵列车了。。。
示例
class Bus:
def __init__(self, passenger=[]): # 默认绑定的参数是列表对象,空列表
self.passenger = passenger
def pick(self, name):
"""上车"""
self.passenger.append(name)
def drop(self, name):
"""下车"""
self.passenger.remove(name)
bus1 = Bus(['sun', 'li']) # 如果实例化时传入乘客,没有任何问题
print(bus1.passenger)
bus2 = Bus()
bus2.pick('zhao')
print(bus2.passenger)
bus3 = Bus()
print(bus3.passenger)
打印 ['sun', 'li'] ['zhao'] ['zhao']
以上演示,会惊奇的发现,bus3.passenger竟然不是空,而是延续了bus2内的数据。
如果Bus类实例化时,不指定乘客,这时self.passenger变成了passenger参数的默认值的别名。
默认值在定义函数时计算,因此默认值变成了函数对象的属性。如果默认值是可变对象,而且修改了它的值,那么后续的函数调用都会受到影响。
我对于这个现象的理解:init函数执行时,实参passenger是对于一个空列表的引用,形参self.passenger是passenger实参的别名,对于self.passenger的修改,就是等价于对原始空列表的修改,后续的函数对这个列表的引用,都会读取到这一个列表的数据。
如果入参是可变类型,注意创建副本!
上面说到了默认参数不能为可变类型,规避了这个以后,还有一个问题:
class Bus:
def __init__(self, passenger=None):
if passenger:
self.passenger = passenger # self.passenger是passenger的别名
else:
self.passenger = []
def pick(self, name):
"""上车"""
self.passenger.append(name)
def drop(self, name):
"""下车"""
self.passenger.remove(name)
name_list = ['zhao', 'li', 'sun']
bus = Bus(name_list)
bus.drop('zhao')
print(name_list)
打印结果 ['li', 'sun']
把可变参数列表传递给类对象以后,经过实例的操作,把原列表也改变了,这样直接破坏了原数据。
正确的做法是类对象自己维护一份副本数据,使用浅拷贝。
self.passenger = list(passenger) #使用list创建了passenger列表的副本。