1. 程式人生 > >Python 3.7.1 模組 資料型別 淺和深拷貝操作 copy

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