得之我幸 失之我命

when someone abandons you,it is him that gets loss because he lost someone who truly loves him but you just lost one who doesn’t love you.

Python 深拷贝

写 python 的过程中,拷贝总是少不了的,遥记得 C++ 里有个东西叫深拷贝(总之涉及到指针就变得啥都不好理解了),同为面向对象,但是 python 不是没有指针吗?

这还得从 python 的变量开始说起

变量和对象

在 Python 中,一个变量可以说是内存中的一个对象的 “标签” 或 “引用”,它指向内存中的对象,当一个对象没有任何标签或引用指向它时,它就会被自动释放

1
2
3
a = 1  # 现在变量 a 指向内存中的一个 int 型的对象(a 相当于 1 的名字)
a = 2 # 给 a 重新赋值,此时 a 依旧指向内存中的一个 int 型的对象(a 现在是 2 的名字)。原来的值为 1 的 int 型对象仍然存在,只是不能再通过 a 去访问它
b = a # 把变量 a 赋给 b,相当于给当前内存中对象(此处的 2)增加一个名字

综上,变量不需要类型,类型是属于对象的,这也是 Python 中的变量可以被任何类型赋值的原因

Everything is Object in Python

Python 使用对象模型来储存数据,任何类型的值都是一个对象。所有的 python 对象都有 3 个特征:身份类型

  1. 身份:每一个对象都有自己的唯一的标识,可以使用内建函数 id() 来得到它。这个值可以被认为是该对象的内存地址

  2. 类型:对象的类型决定了该对象可以保存的什么类型的值,可以进行什么操作,以及遵循什么样的规则。type()函数来查看 python 对象的类型

  3. 值:对象表示的数据项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> a = 1
>>> id(a)
140720048772880
>>> b = 2
>>> id(b)
140720048772912
>>> c = a
>>> id(c)
140720048772880
# 运算符 is、is not 就是通过 id() 的返回值(即身份)来判定的,也就是看它们是不是同一个对象的 “标签”
>>> c is a
True
>>> c is b
False
>>> a is not b
True

可变对象与不可变对象

Python 3 中有 6 种标准的数据类型:Numbers、Strings、List、Tuple、Sets、Dictionaries,其中 Numbers、Strings 和 Tuple 是不可更改的对象,而 List、Dictionaries 是可以修改的对象,那么可变与不可变有什么区别呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 不可变对象
def foo(a):
a = 5
print(a)

x = 1
foo(x)
5
print(x)
1
# 这段代码把 x 作为参数传递给函数,这时 x 和 a 都指向内存中值为 1 的对象。然后在函数中 a = 5 时,因为 int 对象不可改变,于是创建一个新的 int 对象(值为 5)并且令 a 指向它。而 x 仍然指向原来的值为 1 的 int 对象,所以函数没有改变 x 变量

def foo(a):
b = 5
a = b
print(a)

x = 1
foo(x)
5
print(x)
1
# 这段代码把 x 作为参数传递给函数,这时 x 和 a 都指向内存中值为 1 的对象。然后在函数中 b = 5 时,创建一个新的 int 对象(值为 5)并且令 b 指向它,a = b 时,a 也指向了 5 这个对象。而 x 仍然指向原来的值为 1 的 int 对象,所以函数没有改变 x 变量

# 可变对象
def foo(a):
a.append(7)
print(a)

x = [1, 2]
foo(x)
[1, 2, 7]
print(x)
[1, 2, 7]
# 这段代码同样把 x 传递给函数 foo,那么 x 和 a 都会指向同一个 list 类型的对象。因为 list 对象是可以改变的,函数中使用 append 在其末尾添加了一个元素,list 对象的内容发生了改变,但是 x 和 a 仍然是指向同一个 list 对象,所以变量 x 的内容发生了改变

深、浅拷贝

在深拷贝之前,得先知道什么是浅拷贝

浅拷贝是指创建一个新的对象,其内容是原对象中元素的引用(拷贝组合对象,不拷贝子对象)

常见的浅拷贝有:切片操作、工厂函数、对象的 copy() 方法、copy 模块中的 copy 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a = [1, 2, 3]
>>> b = list(a)
>>> id(a)
1997304139008
>>> id(b)
1997304191296
>>> for i in range(len(a)):
... print(id(a[i]), id(b[i]))
...
140720048772880 140720048772880
140720048772912 140720048772912
140720048772944 140720048772944
# a 浅拷贝得到 b,a 和 b 指向内存中不同的 list 对象,但它们的元素却指向相同的 int 对象

深拷贝是指创建一个新的对象,然后递归的拷贝原对象所包含的子对象。深拷贝出来的对象与原对象没有任何关联

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a = [1, 2, 3]
>>> import copy
>>> c = copy.deepcopy(a)
>>> id(a)
1997304139008
>>> id(c)
1997304138880
>>> for i in range(len(a)):
... print(id(a[i]), id(c[i]))
...
140720048772880 140720048772880
140720048772912 140720048772912
140720048772944 140720048772944 # 怎么还是一样的?

为什么使用了深拷贝,a 和 c 中元素的 id 还是一样呢?

因为对于不可变对象,当需要一个新的对象时,python 可能会返回已经存在的某个类型和值都一致的对象的引用。而且这种机制并不会影响 a 和 c 的相互独立性,因为当两个元素指向同一个不可变对象时,对其中一个赋值不会影响另外一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
>>> import copy
>>> a = [[1, 2],[5, 6], [8, 9]]
>>> b = copy.copy(a) # a 浅拷贝得到 b
>>> c = copy.deepcopy(a) # a 深拷贝得到 c
>>> print(id(a), id(b))
1997304191360 1997304139008 # a 和 b 不同
>>> for i in range(len(a)):
... print(id(a[i]), id(b[i]))
...
1997304138816 1997304138816 # a 和 b 的子对象相同
1997304193344 1997304193344
1997304191936 1997304191936
>>> print(id(a), id(c))
139832578518984 139832578622456 # a 和 c 不同
>>> for i in range(len(a)):
... print(id(a[i]), id(c[i]))
...
1997304138816 1997304270464
1997304193344 1997304269568
1997304191936 1997304270272 # a 和 c 的子对象不同
>>> a[0][0] = 7
>>> a
[[7, 2], [5, 6], [8, 9]]
>>> b
[[7, 2], [5, 6], [8, 9]]
>>> c
[[1, 2], [5, 6], [8, 9]]

此时,对于可变对象而言,浅拷贝带来的潜在隐患就一目了然了

be yourself, everyone else is already taken.