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。