Python中的append方法是一个常用的方法,可以将一个对象添加到列表末尾。
例如:
list = [1,2,3]
list.append(4)
# 得到的新的列表就变成了[1,2,3,4]
考虑这样一段代码,
a = [1,2]
b = []
b.append(a)
print(b)
a.append(0)
print(b)
他的执行效果如下,明明两次打印之间并没有对b操作,那么为什么b会发生改变呢?
[[1, 2]]
[[1, 2, 0]]
原来,append方法是浅拷贝。在python中,对象赋值实际上是对象的引用,当创建一个对象,然后把它赋值给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用,这就是浅拷贝。
在C语言中,在声明变量的时候,int a,int b,这两条语句为a,b两个变量分别赋予了两块不同的内存空间,然后赋值的时候再将相应的值存储到对应的存储空间。
但是在python中变量的赋值与C语言是截然不同的,考虑下面的代码,
a = 1
b = 1
print(id(a)) # id函数用于获取对象的内存地址
print(id(b))
运行结果为,
2021455792
2021455792
#a,b的内存空间是一样的。
在python中,先生成对象,变量再对对象进行引用,在本例中,1就是对象,然后a再对1进行引用,由于常数是不可变类型,所以1的内存空间是一样的,所以a,b引用的是用一块内存空间。虽然变量名不一样,但是他们引用的对象是相同的。
回到文章最开始的代码。在开始的问题中,
a = [1,2]
b = []
b.append(a)
print(b)
a.append(0)
print(b)
我们逐步来看。首先,b.append(a)就是对a进行了浅拷贝,结果为b=[[1,2]],但b[0]与a引用的对象是相同的,下面可以通过id函数进行验证,
a = [1,2]
b = []
b.append(a)
print(id(a))
print(id(b[0]))
执行结果为:
23768904
23768904
可见,b[0]与a指向同个内存地址。
下一步,代码执行a.append(0),列表是可变类型(*见后文补充可变类型和不可变类型的知识),这一步在原地址的列表的末尾添加0,原地址的内容被改变了但是地址没有变,所以a和b[0]的内容同时被改变了,这就是为什么对a进行append操作b会跟着发生改变。
发生这些的前提是对同一个地址上的内容进行操作,所以影响了指向该地址的所有变量。
如果是进行下面的操作,则是另一种结果:
a = [1,2]
b = []
b.append(a)
print(b)
a = [1,2]
a.append(0)
print(b)
执行结果为:
[[1, 2]]
[[1, 2]]
只是多加了一条语句,b就不会随着a的改变而改变,原因就是第二句a=[1,2]重新生成了一个不同的对象(列表list是可变类型,当存在多个值相同的list类型变量时,指向的内存地址不同),a引用了这个对象,这时候就跟b没有关系了。
补充知识:Python的可变类型与不可变类型
Python的每个对象都分为可变和不可变,主要的核心类型中,数字、字符串、元组是不可变的,列表、字典是可变的。
不可变类型:
对不可变类型的变量重新赋值,实际上是重新创建一个不可变类型的对象,并将原来的变量重新指向新创建的对象(如果没有其他变量引用原有对象的话(即引用计数为0),原有对象就会被回收)。
值不同,内存地址不同。例如,假设i=5, 执行 i += 1 时,内存地址都会变化,实际上 i += 1 并不是真的在原有的int对象上+1,而是重新创建一个value为6的int对象,i引用自这个新的对象。因此int 类型是不可变的。
值相同,内存地址相同。对于不可变类型int,无论创建多少个不可变类型(如i , j , k),只要值相同,都指向同个内存地址。
另,浮点类型是不可变类型。但,修改代码声明两个相同值的浮点型变量,查看它们的id,发现它们并不是指向同个内存地址,这点和int类型不同(这方面涉及Python内存管理机制,Python对int类型和较短的字符串进行了缓存,无论声明多少个值相同的变量,实际上都指向同个内存地址)。
可变类型:
以list为例。list在append之后,还是指向同个内存地址,因为list是可变类型,可以在原处修改。
当存在多个值相同的list类型变量时,指向的内存地址不同。我们也可以通过b = a 的赋值语句,让他们指向同个内存地址。这个时候需要注意,因为a、b指向同个内存地址,而a、b的类型都是List,可变类型,对a、b任意一个List进行修改,都会影响另外一个List的值。