当前位置: 云端笔记 » 编程 » Python » 【Python】深拷贝与浅拷贝

【Python】深拷贝与浅拷贝

阅读本章节前,建议先查看 “数据类型与操作方法” 在 Python 中,浅拷贝(Shallow Copy)和深拷贝(Deep Copy)的核心区别在于对嵌套对象(如列表中的列表、字典中的列表等)的处理方式不同。 下面我们分不同数据类型来探讨:

int

示例1:

import copy

num1 = 100
num2 = copy.copy(num1)
num3 = copy.deepcopy(num1)
num4 = num1

print(id(100), id(num1), id(num2), id(num3), id(num4))

运行结果

4378359832 4378359832 4378359832 4378359832 4378359832

5个结果都是一样的,其实这个比较好理解,int类型属于不可变类型,100在-5~256范围内,符合小整数缓存机制,所以内存中100始终只有一个内存地址,无论浅拷贝还是深拷贝,还是赋值,都是同一份数据

既然都是指向同一份数据,那如果修改其中任何一个值,其他是否会修改呢? 示例2:

import copy

num1 = 100
num2 = copy.copy(num1)
num3 = copy.deepcopy(num1)
num4 = num1

print(id(100), id(num1), id(num2), id(num3), id(num4))

num4 = 200
print("num4修改后的id", id(num4))
print(id(100), id(num1), id(num2), id(num3))

运行结果

4314560536 4314560536 4314560536 4314560536 4314560536
num4修改后的id 4314563736
4314560536 4314560536 4314560536 4314560536

结果显示虽然num4修改为200,但是num1,num2,num3都是一样的。如果您阅读过本站关于Python的类与对象,num4=200发生一下操作:

  • 200 在内存中是否已经存在
    • 存在 将存在的地址复制给变量num4,num4存储的地址改变了
    • 不存在 开辟新的内存空间(其实是小内存对象,底层不一样会真正分配新内存,但是反正使用了不一样的内存),然后200,将这个新内存地址赋值给变量num4

float

示例3:

import copy

num1 = 100.123
num2 = copy.copy(num1)
num3 = copy.deepcopy(num1)
num4 = num1

print(id(100.123), id(num1), id(num2), id(num3), id(num4))

num4 = 200.123
print("num4修改后的id", id(num4))
print(id(100.123), id(num1), id(num2), id(num3))

运行结果

4337495248 4337495248 4337495248 4337495248 4337495248
num4修改后的id 4337497680
4337495248 4337495248 4337495248 4337495248

flat与int类型一样

str

示例4:

import copy

str1 = "hello"
str2 = copy.copy(str1)
str3 = copy.deepcopy(str1)
str4 = str1

print(id("hello"), id(str1), id(str2), id(str3), id(str4))

str4 = "world"
print("str4修改后的id", id(str4))
print(id("world"), id(str1), id(str2), id(str3))

输出结果

4307657632 4307657632 4307657632 4307657632 4307657632
str4修改后的id 4308657392
4308657392 4307657632 4307657632 4307657632

str与int类型一样

bool

示例5:

import copy

f1 = True
f2 = copy.copy(f1)
f3 = copy.deepcopy(f2)
f4 = f1

print(id(True), id(f1), id(f2), id(f3), id(f4))

f4 = False
print("f4修改后的id", id(f4))
print(id(False), id(f1), id(f2), id(f3))

输出结果

4337672392 4337672392 4337672392 4337672392 4337672392
f4修改后的id 4337672360
4337672360 4337672392 4337672392 4337672392

bool与int类型一样

tuple

示例6:

import copy

t1 = ("name", "age")
t2 = copy.copy(t1)
t3 = copy.deepcopy(t2)
t4 = t1

print(id(True), id(t1), id(t2), id(t3), id(t4))

t4 = ("name2", "age2")
print("t4修改后的id", id(t4))
print(id(False), id(t1), id(t2), id(t3))

输出结果

4303790280 4306480128 4306480128 4306480128 4306480128
t4修改后的id 4306565952
4303790248 4306480128 4306480128 4306480128

tuple与int类型一样

list/set/dict

list属于可变类型,有中间表 示例7(不可变元素列表):


import copy

list1 = [1, 2, 3, 4, 5, 6, 7, 8]
list2 = copy.copy(list1)
list3 = copy.deepcopy(list1)
list4 = list1

print(id(list1), id(list2), id(list3), id(list4))
print("list1=", list1, "list2=", list2,  "list3=", list3, "list4=", list4)

list1[0] = 100
print(id(list1), id(list2), id(list3), id(list4))
print("list1=", list1, "list2=", list2,  "list3=", list3, "list4=", list4)

list2[1] = 200
print(id(list1), id(list2), id(list3), id(list4))
print("list1=", list1, "list2=", list2,  "list3=", list3, "list4=", list4)

list3[2] = 300
print(id(list1), id(list2), id(list3), id(list4))
print("list1=", list1, "list2=", list2,  "list3=", list3, "list4=", list4)

运行结果

4311502912 4311501120 4313167744 4311502912
list1= [1, 2, 3, 4, 5, 6, 7, 8] list2= [1, 2, 3, 4, 5, 6, 7, 8] list3= [1, 2, 3, 4, 5, 6, 7, 8] list4= [1, 2, 3, 4, 5, 6, 7, 8]
4311502912 4311501120 4313167744 4311502912
list1= [100, 2, 3, 4, 5, 6, 7, 8] list2= [1, 2, 3, 4, 5, 6, 7, 8] list3= [1, 2, 3, 4, 5, 6, 7, 8] list4= [100, 2, 3, 4, 5, 6, 7, 8]
4311502912 4311501120 4313167744 4311502912
list1= [100, 2, 3, 4, 5, 6, 7, 8] list2= [1, 200, 3, 4, 5, 6, 7, 8] list3= [1, 2, 3, 4, 5, 6, 7, 8] list4= [100, 2, 3, 4, 5, 6, 7, 8]
4311502912 4311501120 4313167744 4311502912
list1= [100, 2, 3, 4, 5, 6, 7, 8] list2= [1, 200, 3, 4, 5, 6, 7, 8] list3= [1, 2, 300, 4, 5, 6, 7, 8] list4= [100, 2, 3, 4, 5, 6, 7, 8]

我们总结下运行结果:

  • 改变list的表的内容,引用它的变量内存地址永远不变。
  • 赋值操作变量list4的内存地址始终等于list1,说明list1与list4始终指向同一快内存地址,可以一个人有两个名字,所以list1修改后list4也发生了变化
  • 浅拷贝与深拷贝变量的内存地址都发生了变化 *三个列表修改互相没有影响,修改后说明中间都没有收到影响,至少说明中间表是独立的。在未作任何修改前输出值都一样:说明中间表都是复制于list1

我们总结出深拷贝与浅拷贝两个结论:

  • 列表元类数据被复制,即列表变量指向的内存地址改变了,赋给了新变量list2与list3,所以id(list2)及id(list3)与id(list1)值不同。
  • 中间表被复制了,因为都是不可变类型,就算独立空间,所有相同值的数字地址都会一样,只能确认到中间表被复制了,都在内存中有独立的内存空间存放,如果一样肯定会互相影响。

示例8(含有可变元素的列表):

import copy

list1 = [1, [20, 21, 23], 3]
list2 = copy.copy(list1)
list3 = copy.deepcopy(list1)
list4 = list1

print(list1[1] is list2[1])

print(list1[1] is list3[1])

print(id(list1), id(list2), id(list3), id(list4))
print("list1=", list1, "list2=", list2,  "list3=", list3, "list4=", list4)

list1[1][0] = 100
print(id(list1), id(list2), id(list3), id(list4))
print("list1=", list1, "list2=", list2,  "list3=", list3, "list4=", list4)

list2[1][1] = 200
print(id(list1), id(list2), id(list3), id(list4))
print("list1=", list1, "list2=", list2,  "list3=", list3, "list4=", list4)

list3[1][2] = 300
print(id(list1), id(list2), id(list3), id(list4))
print("list1=", list1, "list2=", list2,  "list3=", list3, "list4=", list4)

运行结果

True
False
4375939392 4376377216 4377182784 4375939392
list1= [1, [20, 21, 23], 3] list2= [1, [20, 21, 23], 3] list3= [1, [20, 21, 23], 3] list4= [1, [20, 21, 23], 3]
4375939392 4376377216 4377182784 4375939392
list1= [1, [100, 21, 23], 3] list2= [1, [100, 21, 23], 3] list3= [1, [20, 21, 23], 3] list4= [1, [100, 21, 23], 3]
4375939392 4376377216 4377182784 4375939392
list1= [1, [100, 200, 23], 3] list2= [1, [100, 200, 23], 3] list3= [1, [20, 21, 23], 3] list4= [1, [100, 200, 23], 3]
4375939392 4376377216 4377182784 4375939392
list1= [1, [100, 200, 23], 3] list2= [1, [100, 200, 23], 3] list3= [1, [20, 21, 300], 3] list4= [1, [100, 200, 23], 3]

我们已经分析示例7确认中间表都会复制过来。但是如果列表中含有可变类型时:我们可变类型,虽然列表值完全一样,但是可以重新开辟内存空间存放的。中间表是内存连续的指针数组,存放的是对应数据的内存地址,那么可变类型的内是否会都一样的呢? 前两行的打印结果已经表明:

  • 浅拷贝出来的list2中存放的可变类型类表[20, 21, 23]地址与list1完全一样。所以list2的中间表是完整地从list1拷贝过来的。
  • 深拷贝出来的list3中存放的可变类型类表[20, 21, 23]地址与list1不一样。所以list3的中间表是从list1拷贝过来的,但是遇到可变类型会重新开辟空间,将数据复制过来后再将新地址覆盖原来的值

既然不可量类型大家都一样,深拷贝会开辟独立内存存放可变类型数据,那深拷贝就是完全独立的数据。而浅拷贝会保留可变类型数据的内存地址。

现在可以做最终总结了:

  • 浅拷贝:
    • 拷贝元数据,存放独立内存空间
    • 拷贝中间表,存放独立空间,中间表不修改
  • 深拷贝:
    • 拷贝元数据,存放独立内存空间
    • 拷贝中间表,存放独立空间,中间表对可变类型数据开辟新内存存放,其余不变
  • 赋值: 给原来的变量加一个新名称,就是同一个

其余运行结果完全符合我们的结论。

===========================================================================================================
                                 【 Python 赋值 vs 浅拷贝 vs 深拷贝 内存全景图 】
===========================================================================================================

 【 栈区 (Stack) 】             【 堆区 (Heap) —— 第一、二层:外壳元数据与中间表 】                     【 堆区 (Heap) —— 第三层:真实数据实体 】
   变量名 (指针)
                                                                                            ┌───► [ 不可变小整数 1 ] (全局单例池,全天下复用)
    list1 ───┐                                                                              │
             ├───► 【 独立内存: 元数据 A 】 ───► 【 独立内存: 中间表 A 】 ────────────────────┤
    list4 ───┘      - 记录 ma_used=3                    - 0号格子: [ 指针 ]                 │
   (★ 赋值:          - 记录 中间表A地址                  - 1号格子: [ 指针 ──► 0xAAAA ] ─┐   └───► [ 不可变小整数 3 ] (全局单例池,全天下复用)
    同个外壳多贴                                        - 2号格子: [ 指针 ]             │
    一个别名标签)                                                                       │
                                                                                        │
                                                                                        ▼
    list2 ───────► 【 独立内存: 元数据 B 】 ───► 【 独立内存: 中间表 B 】                【 旧嵌套列表实体 (内存地址: 0xAAAA) 】
   (★ 浅拷贝:        - 记录 ma_used=3                    - 0号格子: [ 指针 ] ───────────┤   | - 0号格子: [ 指针 ──► 20 ]
    独立开辟了          - 记录 中间表B地址                  - 1号格子: [ 指针 ──► 0xAAAA ] ─┘   | - 1号格子: [ 指针 ──► 21 ]
    两块新内存,                                        - 2号格子: [ 指针 ]                 +---------------------------------------------+
    但内部指针直接                                                                             ▲ (★ 浅拷贝共享这里,改动内层会引爆联动灾难!)
    复制旧地址)                                                                                │
                                                                                               │
                                                                                               │
    list3 ───────► 【 独立内存: 元数据 C 】 ───► 【 独立内存: 中间表 C 】 ─────────────────────┘ (★ 深拷贝遇到不可变整数,触发免死金牌退化复用)
   (★ 深拷贝:        - 记录 ma_used=3                    - 0号格子: [ 指针 ] 
    开辟全新表C,      - 记录 中间表C地址                  - 1号格子: [ 指针 ──► 0xZZZZ ] ─┐
    遇到可变列表                                        - 2号格子: [ 指针 ]             │
    彻底在底层造                                                                        ▼
    完全隔离的新房)                                                                  【 新嵌套列表实体 (内存地址: 0xZZZZ) 】
                                                                                      | - 0号格子: [ 指针 ──► 20 ] (物理绝对隔离的新世界)
                                                                                      | - 1号格子: [ 指针 ──► 21 ]
                                                                                      +---------------------------------------------+
===========================================================================================================

我们贴一下源码中描述

The difference between shallow and deep copying is only relevant for
compound objects (objects that contain other objects, like lists or
class instances).

- A shallow copy constructs a new compound object and then (to the
  extent possible) inserts *the same objects* into it that the
  original contains.

- A deep copy constructs a new compound object and then, recursively,
  inserts *copies* into it of the objects found in the original.

Two problems often exist with deep copy operations that don't exist
with shallow copy operations:

 a) recursive objects (compound objects that, directly or indirectly,
    contain a reference to themselves) may cause a recursive loop

 b) because deep copy copies *everything* it may copy too much, e.g.
    administrative data structures that should be shared even between
    copies

Python's deep copy operation avoids these problems by:

 a) keeping a table of objects already copied during the current
    copying pass

 b) letting user-defined classes override the copying operation or the
    set of components copied

This version does not copy types like module, class, function, method,
nor stack trace, stack frame, nor file, socket, window, nor any
similar types.

Classes can use the same interfaces to control copying that they use
to control pickling: they can define methods called __getinitargs__(),
__getstate__() and __setstate__().  See the documentation for module
"pickle" for information on these methods.

工具简单翻一下:

===========================================================================================================
                               【 Python copy 模块:通用(浅拷贝与深拷贝)操作说明 】
===========================================================================================================

接口摘要(使用简介):
-----------------------------------------------------------------------------------------------------------
        import copy

        x = copy.copy(y)        # 对对象 y 进行【浅拷贝】
        x = copy.deepcopy(y)    # 对对象 y 进行【深拷贝】

如果要引发模块特有的错误,会抛出 copy.Error 异常。

【核心分水岭】:
浅拷贝与深拷贝的区别,仅仅在面对【组合对象 / 复合对象】(即内部包含其他对象的对象,例如列表、字典或类实例)时才具有实际意义。

- 【浅拷贝(Shallow Copy)】:
  会在内存中构造出一个全新的组合对象(即开辟新外壳、新中间表),然后(在可能的最大范围内)将原容器中所包含的【同一个对象实体】的指针地址直接塞入其中。

- 【深拷贝(Deep Copy)】:
  会在内存中构造出一个全新的组合对象,然后通过【递归(层层追杀)】的方式,将原容器中找到的所有子对象实体的【全新克隆副本】塞入其中。

-----------------------------------------------------------------------------------------------------------
【深拷贝面临的两大经典物理痛点】(浅拷贝天然不存在这两个问题):
-----------------------------------------------------------------------------------------------------------
 a) 【循环引用 / 递归对象】:
    有些组合对象会直接或间接地包含一个指向它自己的指针线。这在深拷贝层层递归时,极易导致程序坠入死循环(爆栈)。

 b) 【拷贝过载 / 复制过多】:
    因为深拷贝默认会无脑地去克隆“视线范围内的所有东西”,这可能导致它复制了不该复制的元数据。例如:一些本应该在多个副本之间强行共享的管理型底层数据结构。

-----------------------------------------------------------------------------------------------------------
【Python 官方深拷贝引擎是如何消灭这两个痛点的?】:
-----------------------------------------------------------------------------------------------------------
 a) 【记账雷达机制 (memo 字典)】:
    在当前的单次拷贝搬家过程中,底层会死死维护一张“已经拷贝过的对象 ID 登记表”。一旦发现某个对象已经造过新房子了,直接复用,彻底消灭死循环。

 b) 【允许用户自定义重写】:
    允许用户在自建的类(Class)中,通过重写特定的方法来接管拷贝动作,或者自由指定到底有哪些内部组件需要被拷贝。

注意:当前版本的深拷贝引擎【默认不会去拷贝】以下类型:
模块(module)、类对象(class)、函数(function)、方法(method)、堆栈追踪记录(stack trace)、堆栈帧(stack frame)、以及文件(file)、套接字(socket)、窗口(window)或任何类似的底层系统级类型。

-----------------------------------------------------------------------------------------------------------
【高级自定义控制指南】:
-----------------------------------------------------------------------------------------------------------
开发者在自定义的类中,可以使用与控制“对象序列化(pickle)”完全相同的接口来接管和控制拷贝行为:
他们可以为类定义名为 `__getinitargs__()`、`__getstate__()` 和 `__setstate__()` 的魔术方法。具体细节可以去翻阅 "pickle" 标准库模块的官方文档。
===========================================================================================================

dict都有元数据,中间表,所以dict及set与list的浅拷贝、深拷贝、赋值是一样的。

示例代码9(value不可变类型):

import copy

d1 = {"k1": "v1", "k2": "v2", "k3": "v3"}
d2 = copy.copy(d1)
d3 = copy.deepcopy(d1)
d4 = d1

print(id(d1), id(d2), id(d3), id(d4))
print(f"{'-' * 16}未修改数据{'-' * 16}")
print("d1=", d1, "\nd2=", d2, "\nd3=", d3, "\nd4=", d4)

d1["k1"] = "v11"
print(f"{'-' * 16}修改d1的k1数据{'-' * 16}")
print(id(d1), id(d2), id(d3), id(d4))
print("d1=", d1, "\nd2=", d2, "\nd3=", d3, "\nd4=", d4)

d2["k2"] = "v22"
print(f"{'-' * 16}修改d2的k2数据{'-' * 16}")
print(id(d1), id(d2), id(d3), id(d4))
print("d1=", d1, "\nd2=", d2, "\nd3=", d3, "\nd4=", d4)

d3["k3"] = "v33"
print(f"{'-' * 16}修改d3的k3数据{'-' * 16}")
print(id(d1), id(d2), id(d3), id(d4))
print("d1=", d1, "\nd2=", d2, "\nd3=", d3, "\nd4=", d4)

运行结果

4310962816 4310010688 4311006400 4310962816
d1= {'k1': 'v11', 'k2': 'v2', 'k3': 'v3'} 
d2= {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} 
d3= {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} 
d4= {'k1': 'v11', 'k2': 'v2', 'k3': 'v3'}
----------------修改d2的k2数据----------------
4310962816 4310010688 4311006400 4310962816
d1= {'k1': 'v11', 'k2': 'v2', 'k3': 'v3'} 
d2= {'k1': 'v1', 'k2': 'v22', 'k3': 'v3'} 
d3= {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} 
d4= {'k1': 'v11', 'k2': 'v2', 'k3': 'v3'}
----------------修改d3的k3数据----------------
4310962816 4310010688 4311006400 4310962816
d1= {'k1': 'v11', 'k2': 'v2', 'k3': 'v3'} 
d2= {'k1': 'v1', 'k2': 'v22', 'k3': 'v3'} 
d3= {'k1': 'v1', 'k2': 'v2', 'k3': 'v33'} 
d4= {'k1': 'v11', 'k2': 'v2', 'k3': 'v3'}

结果表现与list完全一致 dict与list不同点在于中间表结构不同,有两层。但是同一次运行,哈希随机种子是一样的,只要key相同,最终获取到的下层的房间号(键值对列表索引)相同。 由于key都是不可变量,只要内容相同,那么内存地址也会相同,不同在于value的内存地址,这就跟。示例value都是不可变类型,所以只要value值相同,内存地址也相同。

示例代码10(value有可变类型):

import copy

d1 = {"k1": [1, 2, 3], "k2": "v2", "k3": "v3"}
d2 = copy.copy(d1)
d3 = copy.deepcopy(d1)
d4 = d1

print(id(d1), id(d2), id(d3), id(d4))
print(f"{'-' * 16}未修改数据{'-' * 16}")
print("d1=", d1, "\nd2=", d2, "\nd3=", d3, "\nd4=", d4)

d1["k1"][0] = 100
print(f"{'-' * 16}修改d1的k1[0]数据{'-' * 16}")
print(id(d1), id(d2), id(d3), id(d4))
print("d1=", d1, "\nd2=", d2, "\nd3=", d3, "\nd4=", d4)

d2["k1"][1] = 200
print(f"{'-' * 16}修改d2的k1[1]数据{'-' * 16}")
print(id(d1), id(d2), id(d3), id(d4))
print("d1=", d1, "\nd2=", d2, "\nd3=", d3, "\nd4=", d4)

d3["k1"][2] = 300
print(f"{'-' * 16}修改d3的k1[2]数据{'-' * 16}")
print(id(d1), id(d2), id(d3), id(d4))
print("d1=", d1, "\nd2=", d2, "\nd3=", d3, "\nd4=", d4)

运行结果

4311192192 4310240064 4313873408 4311192192
----------------未修改数据----------------
d1= {'k1': [1, 2, 3], 'k2': 'v2', 'k3': 'v3'} 
d2= {'k1': [1, 2, 3], 'k2': 'v2', 'k3': 'v3'} 
d3= {'k1': [1, 2, 3], 'k2': 'v2', 'k3': 'v3'} 
d4= {'k1': [1, 2, 3], 'k2': 'v2', 'k3': 'v3'}
----------------修改d1的k1[0]数据----------------
4311192192 4310240064 4313873408 4311192192
d1= {'k1': [100, 2, 3], 'k2': 'v2', 'k3': 'v3'} 
d2= {'k1': [100, 2, 3], 'k2': 'v2', 'k3': 'v3'} 
d3= {'k1': [1, 2, 3], 'k2': 'v2', 'k3': 'v3'} 
d4= {'k1': [100, 2, 3], 'k2': 'v2', 'k3': 'v3'}
----------------修改d2的k1[1]数据----------------
4311192192 4310240064 4313873408 4311192192
d1= {'k1': [100, 200, 3], 'k2': 'v2', 'k3': 'v3'} 
d2= {'k1': [100, 200, 3], 'k2': 'v2', 'k3': 'v3'} 
d3= {'k1': [1, 2, 3], 'k2': 'v2', 'k3': 'v3'} 
d4= {'k1': [100, 200, 3], 'k2': 'v2', 'k3': 'v3'}
----------------修改d3的k1[2]数据----------------
4311192192 4310240064 4313873408 4311192192
d1= {'k1': [100, 200, 3], 'k2': 'v2', 'k3': 'v3'} 
d2= {'k1': [100, 200, 3], 'k2': 'v2', 'k3': 'v3'} 
d3= {'k1': [1, 2, 300], 'k2': 'v2', 'k3': 'v3'} 
d4= {'k1': [100, 200, 3], 'k2': 'v2', 'k3': 'v3'}

与list运行结果表现一致 dict总结:

  • 浅拷贝:
    • 拷贝元数据,存放独立内存空间
    • 拷贝中间表,存放独立空间,中间表不修改
  • 深拷贝:
    • 拷贝元数据,存放独立内存空间
    • 拷贝中间表,存放独立空间,中间表对value为可变类型数据开辟新内存存放,其余不变
  • 赋值: 给原来的变量加一个新名称,就是同一个

源码

"""Generic (shallow and deep) copying operations.

Interface summary:

        import copy

        x = copy.copy(y)        # make a shallow copy of y
        x = copy.deepcopy(y)    # make a deep copy of y

For module specific errors, copy.Error is raised.

The difference between shallow and deep copying is only relevant for
compound objects (objects that contain other objects, like lists or
class instances).

- A shallow copy constructs a new compound object and then (to the
  extent possible) inserts *the same objects* into it that the
  original contains.

- A deep copy constructs a new compound object and then, recursively,
  inserts *copies* into it of the objects found in the original.

Two problems often exist with deep copy operations that don't exist
with shallow copy operations:

 a) recursive objects (compound objects that, directly or indirectly,
    contain a reference to themselves) may cause a recursive loop

 b) because deep copy copies *everything* it may copy too much, e.g.
    administrative data structures that should be shared even between
    copies

Python's deep copy operation avoids these problems by:

 a) keeping a table of objects already copied during the current
    copying pass

 b) letting user-defined classes override the copying operation or the
    set of components copied

This version does not copy types like module, class, function, method,
nor stack trace, stack frame, nor file, socket, window, nor any
similar types.

Classes can use the same interfaces to control copying that they use
to control pickling: they can define methods called __getinitargs__(),
__getstate__() and __setstate__().  See the documentation for module
"pickle" for information on these methods.
"""

import types
import weakref
from copyreg import dispatch_table

class Error(Exception):
    pass
error = Error   # backward compatibility

__all__ = ["Error", "copy", "deepcopy"]

def copy(x):
    """Shallow copy operation on arbitrary Python objects.

    See the module's __doc__ string for more info.
    """

    cls = type(x)

    copier = _copy_dispatch.get(cls)
    if copier:
        return copier(x)

    if issubclass(cls, type):
        # treat it as a regular class:
        return _copy_immutable(x)

    copier = getattr(cls, "__copy__", None)
    if copier is not None:
        return copier(x)

    reductor = dispatch_table.get(cls)
    if reductor is not None:
        rv = reductor(x)
    else:
        reductor = getattr(x, "__reduce_ex__", None)
        if reductor is not None:
            rv = reductor(4)
        else:
            reductor = getattr(x, "__reduce__", None)
            if reductor:
                rv = reductor()
            else:
                raise Error("un(shallow)copyable object of type %s" % cls)

    if isinstance(rv, str):
        return x
    return _reconstruct(x, None, *rv)

_copy_dispatch = d = {}

def _copy_immutable(x):
    return x
for t in (types.NoneType, int, float, bool, complex, str, tuple,
          bytes, frozenset, type, range, slice, property,
          types.BuiltinFunctionType, types.EllipsisType,
          types.NotImplementedType, types.FunctionType, types.CodeType,
          weakref.ref):
    d[t] = _copy_immutable

d[list] = list.copy
d[dict] = dict.copy
d[set] = set.copy
d[bytearray] = bytearray.copy

del d, t

def deepcopy(x, memo=None, _nil=[]):
    """Deep copy operation on arbitrary Python objects.

    See the module's __doc__ string for more info.
    """

    if memo is None:
        memo = {}

    d = id(x)
    y = memo.get(d, _nil)
    if y is not _nil:
        return y

    cls = type(x)

    copier = _deepcopy_dispatch.get(cls)
    if copier is not None:
        y = copier(x, memo)
    else:
        if issubclass(cls, type):
            y = _deepcopy_atomic(x, memo)
        else:
            copier = getattr(x, "__deepcopy__", None)
            if copier is not None:
                y = copier(memo)
            else:
                reductor = dispatch_table.get(cls)
                if reductor:
                    rv = reductor(x)
                else:
                    reductor = getattr(x, "__reduce_ex__", None)
                    if reductor is not None:
                        rv = reductor(4)
                    else:
                        reductor = getattr(x, "__reduce__", None)
                        if reductor:
                            rv = reductor()
                        else:
                            raise Error(
                                "un(deep)copyable object of type %s" % cls)
                if isinstance(rv, str):
                    y = x
                else:
                    y = _reconstruct(x, memo, *rv)

    # If is its own copy, don't memoize.
    if y is not x:
        memo[d] = y
        _keep_alive(x, memo) # Make sure x lives at least as long as d
    return y

_deepcopy_dispatch = d = {}

def _deepcopy_atomic(x, memo):
    return x
d[types.NoneType] = _deepcopy_atomic
d[types.EllipsisType] = _deepcopy_atomic
d[types.NotImplementedType] = _deepcopy_atomic
d[int] = _deepcopy_atomic
d[float] = _deepcopy_atomic
d[bool] = _deepcopy_atomic
d[complex] = _deepcopy_atomic
d[bytes] = _deepcopy_atomic
d[str] = _deepcopy_atomic
d[types.CodeType] = _deepcopy_atomic
d[type] = _deepcopy_atomic
d[range] = _deepcopy_atomic
d[types.BuiltinFunctionType] = _deepcopy_atomic
d[types.FunctionType] = _deepcopy_atomic
d[weakref.ref] = _deepcopy_atomic
d[property] = _deepcopy_atomic

def _deepcopy_list(x, memo, deepcopy=deepcopy):
    y = []
    memo[id(x)] = y
    append = y.append
    for a in x:
        append(deepcopy(a, memo))
    return y
d[list] = _deepcopy_list

def _deepcopy_tuple(x, memo, deepcopy=deepcopy):
    y = [deepcopy(a, memo) for a in x]
    # We're not going to put the tuple in the memo, but it's still important we
    # check for it, in case the tuple contains recursive mutable structures.
    try:
        return memo[id(x)]
    except KeyError:
        pass
    for k, j in zip(x, y):
        if k is not j:
            y = tuple(y)
            break
    else:
        y = x
    return y
d[tuple] = _deepcopy_tuple

def _deepcopy_dict(x, memo, deepcopy=deepcopy):
    y = {}
    memo[id(x)] = y
    for key, value in x.items():
        y[deepcopy(key, memo)] = deepcopy(value, memo)
    return y
d[dict] = _deepcopy_dict

def _deepcopy_method(x, memo): # Copy instance methods
    return type(x)(x.__func__, deepcopy(x.__self__, memo))
d[types.MethodType] = _deepcopy_method

del d

def _keep_alive(x, memo):
    """Keeps a reference to the object x in the memo.

    Because we remember objects by their id, we have
    to assure that possibly temporary objects are kept
    alive by referencing them.
    We store a reference at the id of the memo, which should
    normally not be used unless someone tries to deepcopy
    the memo itself...
    """
    try:
        memo[id(memo)].append(x)
    except KeyError:
        # aha, this is the first one :-)
        memo[id(memo)]=[x]

def _reconstruct(x, memo, func, args,
                 state=None, listiter=None, dictiter=None,
                 *, deepcopy=deepcopy):
    deep = memo is not None
    if deep and args:
        args = (deepcopy(arg, memo) for arg in args)
    y = func(*args)
    if deep:
        memo[id(x)] = y

    if state is not None:
        if deep:
            state = deepcopy(state, memo)
        if hasattr(y, '__setstate__'):
            y.__setstate__(state)
        else:
            if isinstance(state, tuple) and len(state) == 2:
                state, slotstate = state
            else:
                slotstate = None
            if state is not None:
                y.__dict__.update(state)
            if slotstate is not None:
                for key, value in slotstate.items():
                    setattr(y, key, value)

    if listiter is not None:
        if deep:
            for item in listiter:
                item = deepcopy(item, memo)
                y.append(item)
        else:
            for item in listiter:
                y.append(item)
    if dictiter is not None:
        if deep:
            for key, value in dictiter:
                key = deepcopy(key, memo)
                value = deepcopy(value, memo)
                y[key] = value
        else:
            for key, value in dictiter:
                y[key] = value
    return y

del types, weakref
未经允许不得转载:云端笔记 » 【Python】深拷贝与浅拷贝

相关文章

评论 (0)

2 + 4 =

contact