Python3標准庫:copy復制對象


1. copy復制對象

copy模塊包括兩個函數copy()和deepcopy(),用於復制現有的對象。

1.1 淺副本

copy()創建的淺副本(shallow copy)是一個新容器,其中填充了原對象內容的引用。建立list對象的一個淺副本時,會構造一個新的list,並將原對象的元素追加到這個list。

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]))

作為一個淺副本,並不會復制MyClass實例,所以dup列表中的引用會指向my_list中相同的對象。

 

1.2 深副本

deepcopy()創建的深副本是一個新容器,其中填充了原對象內容的副本。要建立一個list的深副本,會構造一個新的list,復制原列表的元素,然后將這些副本追加到新列表。

將前例中的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

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]))

列表的第一個元素不再是相同的對象引用,不過比較這兩個對象時,仍認為它們是相等的。

1.3 定制復制行為

可以使用特殊方法__copy__()和__deepcopy__()來控制如何建立副本。

調用__copy__()而不提供任何參數,這會返回對象的一個淺副本。

調用__deepcopy__(),並提供一個備忘字典,這會返回對象的一個深副本。所有需要深復制的成員屬性都要連同備忘字典傳遞到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)

備忘字典用於跟蹤已復制的值,以避免無限遞歸。

1.4 深副本中的遞歸

為了避免復制遞歸數據結構可能帶來的問題,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)

Graph類包含一些基本的有向圖方法。可以利用一個名和一個列表(包含已連接的現有節點)初始化一個graph實例。add_connection()方法用於建立雙向連接。深復制操作符也用到了這個方法。

__deepcopy__()方法將打印消息來顯示這個方法是如何調用的,並根據需要管理備忘字典內容。它不是復制整個連接列表,而是創建一個新列表,再把各個連接的副本追加到這個列表。這樣可以確保復制各個新節點時會更新備忘字典,而避免遞歸問題或多於的節點副本。與前面一頁,完成時會返回復制的對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM