首页 Python中的拷贝
文章
取消

Python中的拷贝

Python中的拷贝是一个比较基础的概念,其实这个话题已经被很多人讲了很多遍了,但我觉得有必要再探讨一遍,尤其是深拷贝浅拷贝。

深拷贝、浅拷贝

浅拷贝 copy.copy() 与深拷贝 copy.deepcopy() 最主要的区别在于对组合对象的处理不同。

不可变对象

我们先看下对不可变对象的处理:

1
2
3
4
5
6
7
8
9
10
>>> import copy
>>> a=1
>>> b=copy.copy(a)
>>> c=copy.deepcopy(a)
>>> id(a)
4459900080
>>> id(b)
4459900080
>>> id(c)
4459900080

我们可以看到结果是一样的。但有人可能会说到小的整数在 Python 中是优化过的,是共享内存的。

这种质疑很对,Python 中 [-5,256] 区间内的整数内存是复用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> a=-6
>>> b=-6
>>> id(a)==id(b)
False
>>> a=-5
>>> b=-5
>>> id(a)==id(b)
True
>>> a=0
>>> b=0
>>> id(a)==id(b)
True
>>> a=256
>>> b=256
>>> id(a)==id(b)
True
>>> a=257
>>> b=257
>>> id(a)==id(b)
False

我们换用元组来观察浅拷贝与深拷贝:

1
2
3
4
5
6
7
8
9
10
>>> import copy
>>> a=('hi','good morning')
>>> b=copy.copy(a)
>>> c=copy.deepcopy(a)
>>> id(a)
140401911656840
>>> id(b)
140401911656840
>>> id(c)
140401911656840

从此我们了解到浅拷贝与深拷贝对不可变对象处理结果一样,都是拷贝了一个引用。但由于对象是不可变的,拷贝前后的两个对象互不影响。

可变对象

我们以 list 为例。

如果元素为不可变对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> x=[1]
>>> y=copy.copy(x)
>>> z=copy.deepcopy(x)

>>> id(x)
140376059566984
>>> id(y)
140376038573128
>>> id(z)
140376059566344

>>> id(x[0])
4459900080
>>> id(y[0])
4459900080
>>> id(z[0])
4459900080

如果元素为可变对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> X=[{1}]
>>> Y=copy.copy(X)
>>> Z=copy.deepcopy(X)

>>> id(X)
140376035975304
>>> id(Y)
140376059566088
>>> id(Z)
140376059567048

>>> id(X[0])
140376059366088
>>> id(Y[0])
140376059366088
>>> id(Z[0])
140376059366312

我们可以了解到,对于子对象为不可变对象,浅拷贝和浅拷贝结果相同;对于子对象为可变对象,浅拷贝只拷贝了父对象,子对象仍是原对象子对象的引用,深拷贝对整个父子对象都进行了拷贝,与原对象脱离了关系。

简单来说,浅拷贝与深拷贝对于可变复合对象(如 listdictset、类实例)的处理不同。

  • 浅拷贝对可变复合对象完全拷贝父对象为新对象,对子对象只是拷贝了引用,这也就是所谓的“浅”。
  • 深拷贝对可变复合对象不仅完全拷贝了父对象为新对象,还迭代地拷贝了子对象为新对象,这也就是所谓的“深”。

官方的解释,https://docs.python.org/3/library/copy.html

The difference between shallow and deep copying is only relevant for compound >objects (objects that contain other objects, like lists or class instances):

A shallow copy constructs a new compound object and then (to the extent >possible) inserts references into it to the objects found in the original.

A deep copy constructs a new compound object and then, recursively, inserts >copies into it of the objects found in the original.

不同对象的拷贝

list 的拷贝

list[:] 可以实现拷贝,但是是浅拷贝,使用时要注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> a=[1,2,[3,4],{"5"}]
>>> b=a[:]
>>> b
[1, 2, [3, 4], {'5'}]

>>> id(a[0])==id(b[0])
True
>>> id(a[2])==id(b[2])
True
>>> id(a[3])==id(b[3])
True

>>> b[3].add('6')
>>> b
[1, 2, [3, 4], {'5', '6'}]
>>> a
[1, 2, [3, 4], {'5', '6'}]

dict的拷贝

dict.copy() 可以实现拷贝,但是仍然是浅拷贝,使用时要注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> a={'a':1,'b':[2],'c':{'d':1}}
>>> b=a.copy()
>>> b
{'a': 1, 'b': [2], 'c': {'d': 1}}

>>> id(a['a'])==id(b['a'])
True
>>> id(a['b'])==id(b['b'])
True
>>> id(a['c'])==id(b['c'])
True

>>> b['b'].append(3)
>>> b
{'a': 1, 'b': [2, 3], 'c': {'d': 1}}
>>> a
{'a': 1, 'b': [2, 3], 'c': {'d': 1}}

set的拷贝

set.copy() 可以实现拷贝(浅拷贝?深拷贝?其实在这里无差别),但由于set中只能为不可变对象,所以拷贝前后互不影响。

1
2
3
4
>>> a={1,2,3}
>>> b=a.copy()
>>> b
{1, 2, 3}

通用的拷贝

就是 copy.copy()copy.deepcopy()

类的自定义拷贝

类可以通过重写 __copy__()__deepcopy__()方法来定制类对象的浅拷贝和深拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from copy import copy, deepcopy


class Demo(object):
    def __init__(self):
        self.a = 1
        self.b = "b"
        self.c = ["c"]

    def __copy__(self):
        cls = self.__class__
        result = cls.__new__(cls)
        result.__dict__.update(self.__dict__)
        return result

    def __deepcopy__(self, memo):
        cls = self.__class__
        result = cls.__new__(cls)
        memo[id(self)] = result
        for k, v in self.__dict__.items():
            setattr(result, k, deepcopy(v, memo))
        setattr(result, "tip", "I'm deep copied")
        return result
本文由作者按照 CC BY 4.0 进行授权