Python标准库笔记(7) — copy模块

目录[-]

copy-对象拷贝模块;提供了浅拷贝和深拷贝复制对象的功能, 分别对应模块中的两个函数 copy()deepcopy()

1.浅拷贝(Shallow Copies)

copy() 创建的 浅拷贝 是一个新的容器,它包含了对原始对象的内容的引用。也就是说仅拷贝父对象,不会拷贝对象的内部的子对象。即浅复制只复制对象本身,没有复制该对象所引用的对象。比如,当创建一个列表对象的浅拷贝时,将构造一个新的列表,并将原始对象的元素添加给它。

import copy


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]))
             my_list: [<__main__.MyClass object at 0x0000026DFF98D128>]
                 dup: [<__main__.MyClass object at 0x0000026DFF98D128>]
      dup is my_list: False
      dup == my_list: True
dup[0] is my_list[0]: True
dup[0] == my_list[0]: True

上面的浅拷贝实例中,dup 是由 my_list 拷贝而来, 但是 MyClass 实例不会拷贝,所以 dup 列表与 my_list 中引用的是同一个对象。

2.深拷贝(Deep Copies)

deepcopy() 创建的 深拷贝 是一个新的容器,它包含了对原始对象的内容的拷贝。深拷贝完全拷贝了父对象及其子对象。即创建一个新的组合对象,同时递归地拷贝所有子对象,新的组合对象与原对象没有任何关联。虽然实际上会共享不可变的子对象,但不影响它们的相互独立性。

将上面代码换成 deepcopy(),将会发现其中不同:

import copy


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]))
             my_list: [<__main__.MyClass object at 0x000002442E47D128>]
                 dup: [<__main__.MyClass object at 0x00000244352EF208>]
      dup is my_list: False
      dup == my_list: True
dup[0] is my_list[0]: False
dup[0] == my_list[0]: True

列表中的 MyClass 实例不再是同一个的对象引用,而是重新复制了一份, 但是当两个对象被比较时,它们的值仍然是相等的。

3.自定义拷贝行为

可以通过自定义 __copy__()__deepcopy__() 方法来改变默认的拷贝行为。

  • __copy()__ 是一个无参数方法,它返回一个浅拷贝对象;
  • __deepcopy()__ 接受一个备忘(memo)字典参数,返回一个深拷贝对象。需要进行深拷贝的成员属性都应该传递给 copy.deepcopy() ,以及memo字典,以控制递归。(下面例子将解释memo字典)。

下面的示例演示如何调用这些方法:

import copy


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)
__copy__()
__deepcopy__({})

memo字典用于跟踪已经拷贝的值,以避免无限递归。

4.深拷贝中的递归

为了避免拷贝时有递归数据结构的问题, 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)

Graph 类包括一些基本的有向图方法。可以用一个名称和它所连接的现有节点的列表来初始化一个实例。 add_connection() 方法用于设置双向连接。它也被深拷贝操作符使用。

__deepcopy__() 方法打印了它的调用信息,并根据需要管理memo字典内容。它不会复制整个连接列表,而是创建一个新的列表,并将单个连接的副本添加进去。确保在每个新节点被复制时更新memo字典,并且避免递归或重复拷贝节点。与以前一样,该方法在完成时返回拷贝的对象。

Calling __deepcopy__ for Graph(name=root, id=2115579269360)
  Memo dictionary:
    (empty)
  Copying to new object Graph(name=root, id=2115695211072)

Calling __deepcopy__ for Graph(name=a, id=2115695210904)
  Memo dictionary:
    Graph(name=root, id=2115579269360): Graph(name=root, id=2115695211072)
  Copying to new object Graph(name=a, id=2115695211184)

Calling __deepcopy__ for Graph(name=root, id=2115579269360)
  Already copied to Graph(name=root, id=2115695211072)

Calling __deepcopy__ for Graph(name=b, id=2115695210960)
  Memo dictionary:
    Graph(name=root, id=2115579269360): Graph(name=root, id=2115695211072)
    Graph(name=a, id=2115695210904): Graph(name=a, id=2115695211184)
    2115579269360: Graph(name=root, id=2115695211072)
    2115695219408: [Graph(name=root, id=2115579269360), Graph(name=a, id=2115695210904)]
    2115695210904: Graph(name=a, id=2115695211184)
  Copying to new object Graph(name=b, id=2115695211240)

第二次遇到根节点时,如果一个节点被已拷贝时, __deepcopy__() 检测递归,并从memo字典中重用现有的值,而不是创建一个新对象。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏C/C++基础

C/C++ sizeof(上)

sizeof是C/C++中的一个操作符(operator),其作用是返回一个对象或者类型所占的内存字节数,使用频繁,有必须对其有个全面的了解。

811
来自专栏一“技”之长

Swift讲解专题十一——属性 原

        属性将值与类,结构体,枚举进行关联。Swift中的属性分为存储属性和计算属性两种,存储属性用于存储一个值,其只能用于类与结构体,计算属性用于计算...

913
来自专栏Java帮帮-微信公众号-技术文章全总结

JAVA面试题解惑——final、finally和finalize的区别

final、finally和finalize的区别是什么? 这是一道再经典不过的面试题了,我们在各个公司的面试题中几乎都能看到它的身影。final、final...

3566
来自专栏Java帮帮-微信公众号-技术文章全总结

第十三天 面向对象-final static 匿名对象内部类包代码块【悟空教程】

1464
来自专栏java工会

Java基础第一阶段知识点,招实习的面试官都在问这些

2229
来自专栏我是攻城师

Java基础类String了解一下

当你路过一些商场或者地铁口的时候,有没有被千篇一律的"xx健身,了解一下" 所烦到。

1152
来自专栏java工会

Java基础第一阶段知识点,招实习的面试官都在问这些

a) 答:Java源文件被编译成字节码的形式,无论在什么系统环境下,只要有java虚

1361
来自专栏CDA数据分析师

超能教程 十分钟学会 Python!

假设你希望学习Python这门语言,却苦于找不到一个简短而全面的入门教程。那么本教程将花费十分钟的时间带你走入Python的大门。本文的内容介于教程(Totur...

2876
来自专栏编程

Python函数基础

函数是一种设计工具,它能让程序员将复杂的系统分解为可管理的部件 函数用于将相关功能打包并参数化 在Python中可以创建4种函数 全局函数:定义在模块中 //...

2075
来自专栏塔奇克马敲代码

第 12 章 动态内存

1744

扫码关注云+社区

领取腾讯云代金券