Python 3.7.1 模块 数据类型 浅和深拷贝操作 copy

源代码: Lib / copy.py

1. 需求描述

Python中的赋值语句不复制对象,它们在目标和对象之间创建绑定。对于可变或包含可变项的集合,有时需要一个副本,它不会影响另一个副本。该模块提供通用的浅层和深层拷贝操作(如下所述)。

2. 模块方法

copy.copy(x)

返回x的浅拷贝。

copy.deepcopy(x[, memo])

返回x的深拷贝。

exception copy.error

针对特定模块而引发的错误。

3. 区别

**浅复制和深复制之间的区别仅与复合对象(包含其他对象的对象,如列表或类实例)相关

  • 浅拷贝构造新的复合对象,然后(在可能的范围)在原复合对象中找到的对象上插入引用。
  • 深拷贝构造新的复合对象,然后,递归地,在原复合对象中找到的对象上插入拷贝。

3.1 普通实例


def test():
    alist = [ _ for _ in range(3)]
    adict ={"name":"leng","age":24}
    aset  = set(alist)

    b = [99,'abc',alist,adict,aset]

    d = copy.copy(b)
    e = copy.deepcopy(b)
    print("b={} id(b)={}\nd={} id(d)={}\ne={} id(e)={}".format(b,id(b),d,id(d),e,id(e)))
    b[0],alist[0],b[3]['name']=999,-1,"lengfengyuyu"
    aset.pop()
    
    print("修改数值".center(50,"-"))
    print("b={} \nd={} \ne={} ".format(b, d, e,))
    print("内存地址".center(50, "-"))
    print("id(b[0])={},id(b[2])={},id(b[3])={}".format(id(b[0]),id(b[2]),id(b[3])))
    print("id(d[0])={},id(d[2])={},id(d[3])={}".format(id(d[0]), id(d[2]), id(d[3])))
    print("id(e[0])={},id(e[2])={},id(e[3])={}".format(id(e[0]), id(e[2]), id(e[3])))

#输出结果
b=[99, 'abc', [0, 1, 2], {'name': 'leng', 'age': 24}, {0, 1, 2}] id(b)=2674170588744
d=[99, 'abc', [0, 1, 2], {'name': 'leng', 'age': 24}, {0, 1, 2}] id(d)=2674171076488
e=[99, 'abc', [0, 1, 2], {'name': 'leng', 'age': 24}, {0, 1, 2}] id(e)=2674171074888
-----------------------修改数值-----------------------
b=[999, 'abc', [-1, 1, 2], {'name': 'lengfengyuyu', 'age': 24}, {1, 2}] 
d=[99, 'abc', [-1, 1, 2], {'name': 'lengfengyuyu', 'age': 24}, {1, 2}] 
e=[99, 'abc', [0, 1, 2], {'name': 'leng', 'age': 24}, {0, 1, 2}] 
-----------------------内存地址-----------------------
id(b[0])=2674141091312,id(b[2])=2674170373320,id(b[3])=2674141330168
id(d[0])=140725287251856,id(d[2])=2674170373320,id(d[3])=2674141330168
id(e[0])=140725287251856,id(e[2])=2674171202760,id(e[3])=2674141330888

分析:
(1)b中的数据有很多,99'abc'都是不可变的类型,深浅拷贝后和原数据互不影响,你变我不管。
(2)b中的alist,adict,aset都是可变项的集合(也就是复合对象),浅拷贝后依然引用原数据(从内存地址相同可以看出),你变我也变;深拷贝后和原数数据互不影响(从内存地址不同同可以看出),你变我不管。
(3)例子中的d[0]e[0]都指向的是99,分配了相同的内存地址,但因为99不是复合类型,因此就算d[0]发生变化,e[0]也不会跟着改变。

3.2 深拷贝注意事项

深拷贝操作通常存在两个问题(浅拷贝操作不存在):

  • 递归对象(直接或间接包含对自身的引用的复合对象)可能会导致递归循环。
  • 因为深拷贝会复制它可能复制的所有内容,所以可能会在副本之间造成数据共享。

deepcopy()函数可以通过以下方式避免上述问题:

在当前复制过程中,使用一个memo字典保存已经复制的对象的字典,并且让用户定义的类重写拷贝操作或the set of components copied

4. 其它

此模块不复制模块,方法,堆栈跟踪,堆栈帧,文件,套接字,窗口,数组或任何类似类型。它通过返回原始对象来“复制”函数和类(浅和深); 这与pickle模块处理这些方式兼容。

字典的浅拷贝可以使用dict.copy()方法,列表的浅拷贝可以使用整个列表的切片来完成,例如copied_list = original_list[:]

类可以使用相同的接口来控制用于控制 pickling的复制。有关pickle这些方法的信息,请参阅pickle模块的说明。实际上,该copy模块使用copyreg模块中注册的pickle函数。

为了让类定义自己的copy方法,它可以定义特殊的方法__copy__()__deepcopy__()。前者被称为实现浅拷贝操作; 不接受参数。调用后者来实现深拷贝操作; 它传递了一个参数,memo字典。如果__deepcopy__()实现需要对component进行深层复制,则deepcopy()应该以component为第一个参数并将memo字典作为第二个参数。

也可以看看
模快 pickle
讨论用于支持对象状态检索和恢复的特殊方法。

4.1 高级实例

译者注
本实例节选自 sqlmap 项目的AttribDict类,看一下类中是如何定义__deepcopy__()的:

import copy
import types

class AttribDict(dict):
    """
    This class defines the sqlmap object, inheriting from Python data
    type dictionary.

    >>> foo = AttribDict()
    >>> foo.bar = 1
    >>> foo.bar
    1
    """

    def __init__(self, indict=None, attribute=None):
        if indict is None:
            indict = {}

        # Set any attributes here - before initialisation
        # these remain as normal attributes
        self.attribute = attribute
        dict.__init__(self, indict)
        self.__initialised = True
	# 中间的代码省略
    def __deepcopy__(self, memo):
        retVal = self.__class__()
        memo[id(self)] = retVal

        for attr in dir(self):
            if not attr.startswith('_'):
                value = getattr(self, attr)
                if not isinstance(value, (types.BuiltinFunctionType, types.FunctionType, types.MethodType)):
                    setattr(retVal, attr, copy.deepcopy(value, memo))

        for key, value in self.items():
            retVal.__setitem__(key, copy.deepcopy(value, memo))

        return retVal

猜你喜欢

转载自blog.csdn.net/lengfengyuyu/article/details/84866622