在学完Python
函数那一章节时,很自然的的就会想到Python
中函数传参时传值呢?还是传引用?或者都不是?
在回答上面的问题之前我们先来看看下面的代码:
代码1:
def foo(var): var = 2 print(var) #output: 2 a = 1 foo(a) print(a) #output: 1
恩,看似是值传递
代码2:
def bar(var): var.append(1) b = [] print(b) #output:[] bar(b) print(b) #output:[1]
应该是引用传递?有点奇怪吧,为了弄清楚这个问题,我们先来了解一下Python
中变量与对象的关系。
一、变量和对象
我们首先要知道Python
中的“变量”与C/C++
中“变量”是不同的。
在C/C++
中,当你初始化一个变量时,就是声明一块存储空间并写入值。相当于把一个值放入一个盒子里: int a = 1;
现在a
盒子里放了一个整数1
,当给变量a
赋另外一个值时会替换盒子a
里面的内容: a = 2;
当你把变量a
赋给另外一个变量时,会拷贝a
盒子中的值并放入一个新的“盒子”里: int b = a;
但是
在Python
中,一个变量可以说是内存中的一个对象的“标签”或“引用”: a = 1
现在变量a
指向了内存中的一个int
型的对象(a
相当于对象的标签)。如果给a
重新赋值,那么“标签” a
将会移动并指向另一个对象: a = 2
原来的值为1
的int
型对象仍然存在,但我们不能再通过a
这个标识符去访问它了(当一个对象没有任何标签或引用指向它时,它就会被自动释放)。如果我们把变量a
赋给另一个变量,我们只是给当前内存中对象增加一个“标签”而已: b = a
综上所述,在Python
中变量只是一个标签,一个标识符,它指向内存中的对象。故变量并没有类型,类型是属于对象的,这也是Python
中的变量可以被任何类型赋值的原因。
二、可变对象与不可变对象
在Python
的基本数据类型中,我们知道numbers
、strings
和tuples
是不可更改的对象,而list
、dict
是可以修改的对象。那么可变与不可变有什么区别呢?看下面示例:
a = 1 # a指向内存中一个int型对象 a = 2 # 重新赋值
当将a
重新赋值时,因为原来值为1
的对象是不能改变的,所以a
会指向一个新的int
对象,其值为2
。(如下面的图示)
示例2
list1 = [1, 2] # list1指向内存中一个list类型的对象 list1[0] = 2 # 重新赋值list1中第一个元素
因为list
类型是可以改变的,所以第一个元素变更为2
。更确切的说,list1
的第一个元素是int
型,重新赋值时一个新的int
对象被指定给第一个元素,但是对于list1
来说,它所指的列表型对象没有变,只是列表的内容(其中一个元素)改变了。如下图:
现在我们再来看看开始那两段代码:
def foo(var): var = 2 print(var) a = 1 foo(a) print(a)
上面这段代码把a
作为参数传递给函数,这时a
和var
都指向内存中值为1
的对象。然后在函数中var = 2
时,因为int
对象不可改变,于是创建一个新的int
对象(值为2
)并且令var
指向它。而a
仍然指向原来的值为1
的int
对象,所以函数没有改变变量a
。
如下图:
代码2;
def Bar(var): var.append(1) b = [] print(b) Bar(b) print(b)
这段代码把b
传递给函数Bar
,那么b
和var
都会指向同一个list
类型的对象。因为list
对象是可以改变的,函数中使用append
在其末尾添加了一个元素,list
对象的内容发生了改变,但是b
和var
仍然是指向这一个list
对象,所以变量b
的内容也发生了改变。
如下图:
那么Python
中参数传递是传值,还是传引用呢?准确的回答:都不是。之所以不是传值,因为没有产生复制,而且函数拥有与调用者同样的对象。而似乎更像是C++
的传引用,但是有时却不能改变实参的值。所以只能这样说:对于不可变的对象,它看起来像C++中的传值方式;对于可变对象,它看起来像C++中的按引用传递。