0

0

Python 中的浅拷贝与深拷贝:区别与应用场景

狼影

狼影

发布时间:2025-09-04 15:33:09

|

666人浏览过

|

来源于php中文网

原创

浅拷贝创建新容器但共享内部元素,深拷贝递归复制所有层级确保完全独立。Python中通过切片、copy()实现浅拷贝,copy.deepcopy()实现深拷贝,前者高效但修改嵌套可变元素会影响原对象,后者开销大但隔离彻底。

python 中的浅拷贝与深拷贝:区别与应用场景

Python中的浅拷贝与深拷贝,核心在于它们处理复合对象内部元素的方式不同。简单来说,浅拷贝创建了一个新的容器对象,但其内部元素(如果也是对象的话)仍然是原对象的引用;而深拷贝则会递归地创建所有内部元素的独立副本,确保新旧对象之间完全独立。

在Python中,我们经常需要复制一个对象。直接赋值(

b = a
)仅仅是让两个变量名指向了内存中的同一个对象。这在处理可变对象时,往往不是我们想要的结果,因为修改
b
也会影响
a
。这时,拷贝就派上用场了。

浅拷贝:复制一层皮

浅拷贝,顾名思义,就是只复制了对象“最外层”的结构。如果你有一个列表,里面装着数字和另一个列表,浅拷贝会给你一个新的外层列表,但那个内部的列表,新旧对象还是共享同一个。

你可以通过几种方式实现浅拷贝:

立即学习Python免费学习笔记(深入)”;

  1. 切片操作

    [:]
    :对于列表(list)而言,这是最常用也最简洁的方式。

    import copy
    
    original_list = [1, [2, 3], 4]
    shallow_copy_slice = original_list[:]
    print(f"原始列表ID: {id(original_list)}, 浅拷贝列表ID: {id(shallow_copy_slice)}")
    # 输出ID不同,说明是两个不同的列表对象
    print(f"原始列表内部列表ID: {id(original_list[1])}, 浅拷贝内部列表ID: {id(shallow_copy_slice[1])}")
    # 输出ID相同,说明内部列表是共享的
  2. list()
    dict()
    构造函数

    original_dict = {'a': 1, 'b': [2, 3]}
    shallow_copy_constructor = dict(original_dict)
    print(f"原始字典ID: {id(original_dict)}, 浅拷贝字典ID: {id(shallow_copy_constructor)}")
    print(f"原始字典内部列表ID: {id(original_dict['b'])}, 浅拷贝内部列表ID: {id(shallow_copy_constructor['b'])}")
  3. copy.copy()
    函数:这是标准库
    copy
    模块提供的通用浅拷贝方法,适用于任何实现了
    __copy__
    方法的对象,或者对于普通对象,它会创建一个新实例并浅拷贝其属性。

    original_set = {1, 2, 3}
    shallow_copy_set = copy.copy(original_set)
    # 对于集合这种非复合结构,浅拷贝和深拷贝行为上无差异,因为元素是不可变的。
    # 但如果集合元素是可变对象,那就有区别了。

当浅拷贝的内部元素是可变对象时,问题就来了。修改浅拷贝内部的可变对象,原对象也会跟着变,反之亦然。这在调试时可能会让人抓狂,因为数据来源变得模糊不清。

original = [1, [2, 3], 4]
shallow_copy = original[:]
shallow_copy[0] = 100 # 修改顶层元素,不影响original
shallow_copy[1][0] = 99 # 修改内部可变元素,会影响original
print(f"Original after shallow copy modification: {original}") # Output: [1, [99, 3], 4]
print(f"Shallow copy after modification: {shallow_copy}")     # Output: [100, [99, 3], 4]

你看,

original
的第二个元素也被改了,这就是浅拷贝的“陷阱”所在。

深拷贝:独立王国

深拷贝则完全不同,它会递归地复制对象及其所有子对象,直到所有元素都是独立的。这意味着你对深拷贝的任何修改都不会影响到原对象,它们是两个完全独立的“王国”。

实现深拷贝主要依赖

copy
模块中的
copy.deepcopy()
函数。

import copy

original = [1, [2, 3], 4]
deep_copy = copy.deepcopy(original)

print(f"原始列表ID: {id(original)}, 深拷贝列表ID: {id(deep_copy)}")
print(f"原始列表内部列表ID: {id(original[1])}, 深拷贝内部列表ID: {id(deep_copy[1])}")
# 输出ID不同,说明内部列表也是独立的

deep_copy[0] = 100
deep_copy[1][0] = 99
print(f"Original after deep copy modification: {original}") # Output: [1, [2, 3], 4]
print(f"Deep copy after modification: {deep_copy}")         # Output: [100, [99, 3], 4]

这次,无论怎么修改

deep_copy
original
都毫发无损。这通常是我们期望的“复制”行为。

Python中何时需要使用浅拷贝?

说实话,很多人在遇到需要复制对象时,第一反应可能就是“深拷贝万岁”,觉得这样最安全。但浅拷贝在某些特定场景下,反而更高效、更合理。

一个常见的场景是,当你只需要创建一个新的顶层容器,而内部的元素是不可变对象(如数字、字符串、元组),或者你有意图让新旧容器共享内部可变对象时。比如,你有一个用户列表,每个用户对象可能包含一些不变的ID和名字,以及一个可变的“购物车”列表。如果你只是想创建一个新的用户列表,但希望所有用户对象仍然是内存中的同一个,只是列表的顺序或者增减用户不同,那么浅拷贝就足够了。

class User:
    def __init__(self, user_id, name, cart):
        self.user_id = user_id
        self.name = name
        self.cart = cart # 购物车是一个可变列表

    def __repr__(self):
        return f"User(id={self.user_id}, name='{self.name}', cart={self.cart})"

user1 = User(1, "Alice", ["apple", "banana"])
user2 = User(2, "Bob", ["orange"])
all_users = [user1, user2]

# 浅拷贝用户列表
new_users_list = all_users[:]
new_users_list.append(User(3, "Charlie", [])) # 新增一个用户,不影响all_users

print(f"Original users: {all_users}")
print(f"New users list: {new_users_list}")

# 此时,user1和user2对象本身在两个列表中是共享的
user1.cart.append("grape") # 修改user1的购物车,会影响all_users和new_users_list中对应的user1
print(f"Original users after modification: {all_users}")
print(f"New users list after modification: {new_users_list}")

在这个例子中,

new_users_list
all_users
共享
user1
user2
对象。如果我们的业务逻辑允许甚至需要这种共享(例如,用户对象本身是某种全局缓存或单例),那么浅拷贝就是正确的选择。此外,对于性能敏感的应用,如果对象结构很深且复制成本高昂,而你又不需要完全独立,浅拷贝能显著减少时间和内存开销。

科大讯飞-AI虚拟主播
科大讯飞-AI虚拟主播

科大讯飞推出的移动互联网智能交互平台,为开发者免费提供:涵盖语音能力增强型SDK,一站式人机智能语音交互解决方案,专业全面的移动应用分析;

下载

深拷贝的开销与潜在问题是什么?

深拷贝虽然提供了完全的独立性,但它并非没有代价。

性能与内存开销:这是最直接的问题。

deepcopy
需要递归遍历整个对象图,为每个可变子对象创建新的副本。对于大型或深度嵌套的数据结构,这会消耗大量的CPU时间和内存。想象一下一个包含数千个元素的列表,每个元素又是一个包含复杂对象的字典,深拷贝这种结构可能会让程序明显变慢。

递归深度限制:Python解释器有默认的递归深度限制(通常是1000)。如果你的数据结构嵌套层级过深,

deepcopy
在递归过程中可能会超出这个限制,导致
RecursionError
。虽然可以通过
sys.setrecursionlimit()
来提高限制,但这通常不是一个好兆头,可能意味着你的数据结构设计本身就存在问题,或者深拷贝并非最佳方案。

循环引用问题:如果你的对象图存在循环引用(例如,对象A引用了B,B又引用了A),

deepcopy
需要特别处理以避免无限递归。
copy
模块通过一个内部的“备忘录”(memo)字典来跟踪已经复制过的对象,确保每个对象只被复制一次。这增加了
deepcopy
的内部复杂性和开销。

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

a = Node('A')
b = Node('B')
a.next = b
b.next = a # 形成循环引用

# deepcopy可以正确处理循环引用
deep_copied_a = copy.deepcopy(a)
print(f"Original a.next.next is a: {a.next.next is a}") # True
print(f"Deep copied a.next.next is deep_copied_a: {deep_copied_a.next.next is deep_copied_a}") # True
print(f"Original a is deep_copied_a: {a is deep_copied_a}") # False

尽管

deepcopy
能处理,但其内部机制的复杂性也意味着更高的开销。

不可拷贝的对象:并非所有对象都能被深拷贝。有些对象(如文件句柄、网络连接、线程锁、数据库连接等)是系统资源或外部资源的引用,它们不能被简单地复制。尝试深拷贝这些对象可能会引发

TypeError
AttributeError
,或者导致意想不到的副作用。对于自定义类,如果它们包含这类不可拷贝的属性,或者有特殊的拷贝逻辑,你需要实现
__copy__
__deepcopy__
方法来指导
copy
模块如何进行拷贝。

import threading

class MyResource:
    def __init__(self, name):
        self.name = name
        self.lock = threading.Lock() # 线程锁通常不应被深拷贝

    def __deepcopy__(self, memo):
        # 这是一个简单的示例,实际情况可能更复杂
        # 我们可能希望锁是新的,或者根本不拷贝它
        new_instance = MyResource(self.name)
        # new_instance.lock = threading.Lock() # 创建一个新的锁
        # memo[id(self)] = new_instance # 记录已拷贝的对象以处理循环引用
        return new_instance

# 尝试深拷贝一个包含线程锁的对象
resource = MyResource("data")
try:
    # 默认的deepcopy可能会尝试复制lock对象,这通常是无效的
    # 如果没有__deepcopy__,可能会出错或行为异常
    deep_copied_resource = copy.deepcopy(resource)
    print(f"Deep copied resource name: {deep_copied_resource.name}")
    print(f"Original lock ID: {id(resource.lock)}, Deep copied lock ID: {id(deep_copied_resource.lock)}")
except TypeError as e:
    print(f"Error during deepcopy: {e}")

因此,在使用深拷贝前,我们必须仔细评估其必要性、潜在的性能影响,以及对象图中是否存在不可拷贝的元素。

如何避免Python拷贝操作中的常见陷阱?

理解Python的拷贝机制,很大程度上就是理解其对象引用和可变性。避免陷阱,关键在于有意识地思考数据流和对象生命周期。

1. 明确对象的“可变性”:这是理解拷贝行为的基石。Python中的对象分为可变(Mutable)和不可变(Immutable)两种。

  • 不可变对象(如数字、字符串、元组、frozenset):赋值或浅拷贝它们,效果上与深拷贝无异,因为它们的值一旦创建就不能改变。你无法修改它们,只能创建新的对象。
  • 可变对象(如列表、字典、集合、自定义类实例):这是拷贝操作需要特别关注的。修改可变对象的任何部分都会影响所有指向它的引用。

2. 区分“赋值”、“浅拷贝”和“深拷贝”的语义

  • 赋值 (
    a = b
    )
    a
    b
    指向内存中的同一个对象。
  • 浅拷贝 (
    a = b.copy()
    a = copy.copy(b)
    )
    a
    是一个新对象,但它内部的可变元素仍与
    b
    共享。
  • 深拷贝 (
    a = copy.deepcopy(b)
    )
    a
    是一个完全独立的新对象,包括其所有内部元素。

3. 使用

id()
is
进行调试
:当你不确定两个变量是否指向同一个对象时,
id(obj)
可以返回对象的内存地址。如果
id(obj1) == id(obj2)
,那么它们就是同一个对象。
is
操作符(
obj1 is obj2
)直接检查两个变量是否引用同一个对象,比
==
(检查值是否相等)更严格,是判断对象身份的利器。

list1 = [1, [2, 3]]
list2 = list1 # 赋值
list3 = list1[:] # 浅拷贝
list4 = copy.deepcopy(list1) # 深拷贝

print(f"list1 is list2: {list1 is list2}") # True
print(f"list1 is list3: {list1 is list3}") # False
print(f"list1[1] is list2[1]: {list1[1] is list2[1]}") # True
print(f"list1[1] is list3[1]: {list1[1] is list3[1]}") # True (浅拷贝的内部元素是共享的)
print(f"list1[1] is list4[1]: {list1[1] is list4[1]}") # False (深拷贝的内部元素是独立的)

4. 谨慎处理自定义类的拷贝:如果你创建了自己的类,并且它包含可变属性,或者它的实例需要被拷贝,那么你需要考虑如何实现

__copy__
__deepcopy__
方法。

  • __copy__(self)
    :当调用
    copy.copy(obj)
    时,如果类定义了这个方法,就会调用它来执行浅拷贝。
  • __deepcopy__(self, memo)
    :当调用
    copy.deepcopy(obj)
    时,如果类定义了这个方法,就会调用它。
    memo
    是一个字典,用于存储已经拷贝过的对象,以处理循环引用。你需要确保递归调用
    copy.deepcopy()
    来拷贝子对象,并把自身添加到
    memo
    中。
class MyCustomObject:
    def __init__(self, data, nested_list):
        self.data = data
        self.nested_list = nested_list

    def __copy__(self):
        # 实现浅拷贝:创建一个新实例,浅拷贝属性
        cls = self.__class__
        new_obj = cls(self.data, self.nested_list) # 注意这里nested_list是直接引用
        return new_obj

    def __deepcopy__(self, memo):
        # 实现深拷贝:创建一个新实例,深拷贝属性
        cls = self.__class__
        new_obj = cls.__new__(cls) # 创建一个空实例
        memo[id(self)] = new_obj # 记录已拷贝的对象,处理循环引用

        for k, v in self.__dict__.items():
            setattr(new_obj, k, copy.deepcopy(v, memo)) # 递归深拷贝每个属性
        return new_obj

obj1 = MyCustomObject(1, [10, 20])
obj_shallow_copy = copy.copy(obj1)
obj_deep_copy = copy.deepcopy(obj1)

obj_shallow_copy.nested_list.append(30)
print(f"Original obj1.nested_list: {obj1.nested_list}") # [10, 20, 30]

obj_deep_copy.nested_list.append(40)
print(f"Original obj1.nested_list: {obj1.nested_list}") # 仍然是 [10, 20, 30]

正确实现这些特殊方法,能让你的自定义类在拷贝操作中表现得符合预期。

5. 优先考虑不可变数据结构:在某些情况下,如果你的数据不需要被修改,或者你总是希望在修改时创建新版本,那么使用不可变数据结构(如元

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1498

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

623

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

612

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

588

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

170

2025.07.29

c++字符串相关教程
c++字符串相关教程

本专题整合了c++字符串相关教程,阅读专题下面的文章了解更多详细内容。

83

2025.08.07

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
尚学堂ios初级视频教程
尚学堂ios初级视频教程

共77课时 | 17.7万人学习

尚学堂ios初级视频教程
尚学堂ios初级视频教程

共77课时 | 17.7万人学习

最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号