Python使用按引用传递(pass-by-reference)将参数传递到函数中。如果你改变一个函数内的参数,会影响到函数的调用。这是Python的默认操作。不过,如果我们传递字面参数,比如字符串、数字或元组,它们是按值传递,这是因为它们是不可变的。
Python中有一个非常重要的概念——万物皆对象,无论是一个数字、字符串,还是数组、元组、字典,在Python中都会以一个对象的形式存在。
a = 123
对于上面这行代码,在Python看来就是创建一个PyObject对象,值为123,然后定义一个指针a,a指向这个PyObject对象。
可变对象和不可变对象
Python中的对象分为两种类型,可变对象和不可变对象,不可变对象指tuple、str、int等类型的对象,可变对象指的是dict、list、自定义对象等类型的对象,我们用一段代码说明他们的区别。
a = [1, 2, 3]
print(id(a)) # 4540010376
def mutable(a):
print(id(a)) # 4540010376
a += [4]
print(id(a)) # 4540010376
mutable(a)
b = 1
print(id(b)) # 4538493952
def immutable(b):
print(id(b)) # 4538493952
b += 1
print(id(b)) # 4538493984
immutable(b)
输出:
/usr/local/bin/python3.7 /Users/zhangsf/code/python/flask_test/static/run.py
4540010376
4540010376
4540010376
4538493952
4538493952
4538493984
上面代码中我们分别定义了一个可变对象和一个不可变对象,并且对他们进行修改,打印修改前后的对象标识可以发现,对可变对象进行修改,变量对其引用不会发生变化,对不可变对象进行修改,变量引用发生了变化。
通过上面的参数传递代码可以看出,修改传进的可变参数时,会对外部对象产生影响,修改不可变参数时则不会影响。
概括地说,Python参数传递时,既不是传对象也不是传引用,之所以会有上述的区别,跟Python的对象机制有关,参数传递只是给对象绑定了一个新的变量(实际上是传递C中的指针)。
参数传递时的坑
理解了参数传递的逻辑,我们需要注意一下这种逻辑可能引发的问题。
上面的代码的输出,按照可变对象传参的逻辑,应该每次调用都输出[1]才对,而实际输出看上去好像默认参数好像只生效了一次。原因在于Python的函数也是对象(万物皆对象),这个对象只初始化一次,加上参数又是不可变对象,所以每次调用实际上都修改的是一个对象。
def test(b=[]):
b += [1]
print(b)
test() # [1]
test() # [1, 1]
test() # [1, 1, 1]
解决这个问题,推荐再参数传递可变对象时,默认值设置为None,在函数内部对None进行判断后再赋予默认值。
def test(b=None):
b = b or []
b += [1]
print(b)
test() # [1]
test() # [1]
test() # [1]
再看一段代码。
i = 1
def test(a=i):
print(a)
i = 2
test() # 1
由于参数默认值是在函数定义时而不是函数执行时确定的,所以这段代码test方法的参数默认值时1而不是2。