1. 程式人生 > >每週一個 Python 模組 | copy

每週一個 Python 模組 | copy

專欄地址:每週一個 Python 模組

copy 模組包括兩個功能,copy()deepcopy(),用於複製現有物件。

淺拷貝

copy() 建立的淺表副本是一個新容器,是對原始物件內容的引用。

import copy
import functools


@functools.total_ordering
class MyClass:

    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name

    def
__gt__(self, other):
return self.name > other.name a = MyClass('a') my_list = [a] dup = copy.copy(my_list) print(' my_list:', my_list) print(' dup:', dup) print(' dup is my_list:', (dup is my_list)) print(' dup == my_list:', (dup == my_list)) print('dup[0] is my_list[0]:'
, (dup[0] is my_list[0])) print('dup[0] == my_list[0]:', (dup[0] == my_list[0])) # output # my_list: [<__main__.MyClass object at 0x101f9c160>] # dup: [<__main__.MyClass object at 0x101f9c160>] # dup is my_list: False # dup == my_list: True # dup[0] is my_list[0]: True
# dup[0] == my_list[0]: True 複製程式碼

對於淺拷貝,MyClass 例項並不複製,因此dupmy_list 引用的是同一個物件。

深拷貝

將呼叫替換為 deepcopy() 會使輸出明顯不同。

import copy
import functools


@functools.total_ordering
class MyClass:

    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name

    def __gt__(self, other):
        return self.name > other.name


a = MyClass('a')
my_list = [a]
dup = copy.deepcopy(my_list)

print('             my_list:', my_list)
print('                 dup:', dup)
print('      dup is my_list:', (dup is my_list))
print('      dup == my_list:', (dup == my_list))
print('dup[0] is my_list[0]:', (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))

# output
#              my_list: [<__main__.MyClass object at 0x101e9c160>]
#                  dup: [<__main__.MyClass object at 0x1044e1f98>]
#       dup is my_list: False
#       dup == my_list: True
# dup[0] is my_list[0]: False
# dup[0] == my_list[0]: True
複製程式碼

列表的第一個元素不再是相同的物件引用,但是當比較兩個物件時,它們仍然是相等的。

自定義複製行為

可以使用 __copy__()__deepcopy__() 方法來自定義複製行為。

  • __copy__() 不需要引數,返回該物件的淺拷貝副本。
  • __deepcopy__()使用 memo 字典呼叫,並返回該物件的深拷貝物件。任何需要深度複製的成員屬性,都應與 memo 字典一起傳遞給 copy.deepcopy()

以下示例說明了如何呼叫方法。

import copy
import functools


@functools.total_ordering
class MyClass:

    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name

    def __gt__(self, other):
        return self.name > other.name

    def __copy__(self):
        print('__copy__()')
        return MyClass(self.name)

    def __deepcopy__(self, memo):
        print('__deepcopy__({})'.format(memo))
        return MyClass(copy.deepcopy(self.name, memo))


a = MyClass('a')

sc = copy.copy(a)
dc = copy.deepcopy(a)

# output
# __copy__()
# __deepcopy__({})
複製程式碼

memo 字典用於跟蹤已經複製的值,以避免無限遞迴。

深度複製中的遞迴

為避免重複遞迴資料結構的問題,deepcopy() 使用字典來跟蹤已複製的物件。這個字典被傳遞給__deepcopy__() 方法,因此可以在這裡檢查重複遞迴問題。

下一個示例顯示了互連資料結構(如有向圖)如何通過實現__deepcopy__()方法來防止遞迴。

import copy


class Graph:

    def __init__(self, name, connections):
        self.name = name
        self.connections = connections

    def add_connection(self, other):
        self.connections.append(other)

    def __repr__(self):
        return 'Graph(name={}, id={})'.format(
            self.name, id(self))

    def __deepcopy__(self, memo):
        print('\nCalling __deepcopy__ for {!r}'.format(self))
        if self in memo:
            existing = memo.get(self)
            print('  Already copied to {!r}'.format(existing))
            return existing
        print('  Memo dictionary:')
        if memo:
            for k, v in memo.items():
                print('    {}: {}'.format(k, v))
        else:
            print('    (empty)')
        dup = Graph(copy.deepcopy(self.name, memo), [])
        print('  Copying to new object {}'.format(dup))
        memo[self] = dup
        for c in self.connections:
            dup.add_connection(copy.deepcopy(c, memo))
        return dup


root = Graph('root', [])
a = Graph('a', [root])
b = Graph('b', [a, root])
root.add_connection(a)
root.add_connection(b)

dup = copy.deepcopy(root)

# output
# Calling __deepcopy__ for Graph(name=root, id=4326183824)
#   Memo dictionary:
#     (empty)
#   Copying to new object Graph(name=root, id=4367233208)
# 
# Calling __deepcopy__ for Graph(name=a, id=4326186344)
#   Memo dictionary:
#     Graph(name=root, id=4326183824): Graph(name=root, id=4367233208)
#   Copying to new object Graph(name=a, id=4367234720)
# 
# Calling __deepcopy__ for Graph(name=root, id=4326183824)
#   Already copied to Graph(name=root, id=4367233208)
# 
# Calling __deepcopy__ for Graph(name=b, id=4326183880)
#   Memo dictionary:
#     Graph(name=root, id=4326183824): Graph(name=root, id=4367233208)
#     Graph(name=a, id=4326186344): Graph(name=a, id=4367234720)
#     4326183824: Graph(name=root, id=4367233208)
#     4367217936: [Graph(name=root, id=4326183824), Graph(name=a, id=4326186344)]
#     4326186344: Graph(name=a, id=4367234720)
#   Copying to new object Graph(name=b, id=4367235000)
複製程式碼

Graph 類包括幾個基本的有向圖的方法。可以使用名稱和與其連線的現有節點列表初始化例項。add_connection() 方法用於設定雙向連線。它也被深拷貝操作符使用。

__deepcopy__()方法列印訊息以顯示其呼叫方式,並根據需要管理備忘錄字典內容。它不是複製整個連線列表,而是建立一個新列表,並將各個連線的副本新增進去。這確保了備忘錄字典在每個新節點被複制時更新,並且它避免了遞迴問題或節點的額外副本。和以前一樣,該方法在完成後返回複製的物件。

digraph copy_example {“root”;  “a” - >“root”;  “b” - >“root”;  “b” - >“a”;  “root” - >“a”;  “root” - >“b”;  }

具有迴圈的物件圖的深層複製

圖中顯示的圖形包括幾個週期,但使用備註字典處理遞迴可防止遍歷導致堆疊溢位錯誤。

第二次根遇到一個節點,而這個節點被複制,__deepcopy__()檢測該遞迴和重用來自備忘錄字典現有值而不是建立新的物件。

相關文件:

pymotw.com/3/copy/inde…