首先要理解python中的变量只是一个标注,不是真正的值。
对象标识,值和别名
先看看这个例子:
a = [1,2]
b = a
a.append(3)
b
Out[4]: [1, 2, 3]
a,b都是指向了同一个对象。
id(a)
Out[5]: 2101610153608
id(b)
Out[6]: 2101610153608
也就是说,赋值指的是对象的引用。
a,b指代了同一个对象
a is b
Out[8]: True
也表明了a,b是指代同一对象,再看看这个
c = [1,2,3]
a == c
Out[11]: True
a is c
Out[12]: False
b是a别名,c却不是a的别名,因为a和c绑定的不是同一个对象。具体的看官方文档
Every object has an identity, a type and a value. An object’s identity never changes once it has been created; you may think of it as the object’s address in memory. The ‘is‘ operator compares the identity of two objects; the id() function returns an integer representing its identity.
现在我们就可以看看is和==的区别,is比较的是对象的标识,==比较两个对象的值(对象中保存的数据)。is比==快,因为is是不能重载的,而a==b是语法糖(这个和scala一样),等同于
a.__eq__(b)。
元组的相对不可变性
a = (1,2,[3,4])
b = (1,2,[3,4])
a == b
Out[15]: True
a[2].append(2)
a
Out[20]: (1, 2, [3, 4, 2])
id(a)
Out[21]: 2101633577464
a == b
Out[22]: False
那么你想要复制列表要怎么做呢?
a = [1,2]
b = list(a)
a == b
Out[25]: True
a is b
Out[26]: False
通过内置的数据类型的构造方法实现了浅复制。
但是
a = [1,[2,3]]
b = list(a)
a.append(4)
a
Out[35]: [1, [2, 3], 4]
b
Out[36]: [1, [2, 3]]
a[1].remove(2)
b
Out[38]: [1, [3]]
这个a,b虽然是不同的列表,但是内部的列表却是指向同一个对象,这也就是浅的含义。
深呢,就是副本不共享内部对象
a = [1,[2,3]]
b = copy.deepcopy(a)
a[1].remove(2)
b
Out[48]: [1, [2, 3]]
有一个问题就是循环引用的问题,对象会引用不该复制的外部资源或单例值,这时候就要自己实现__deepcopy__方法了
引用和函数参数
函数的传递模式呢,指的是函数的各个形式参数获得实参中各个引用的副本。函数内部的形参是实参的别名,也就是函数可以修改
作为参数传入的可变对象,但是无法修改它们的标识。
def f(a,b):
a += b
return a
x = 1
y = 2
f(x, y)
Out[52]: 3
x = [1,2]
y = [3]
f(x, y)
Out[55]: [1, 2, 3]
x
Out[56]: [1, 2, 3]
当x是可变的列表时,实际上经过一个函数,已经发生了改变。