简明扼要!理解Python浅拷贝和深拷贝

问题

  • 需要拷贝一个对象时,意外共享引用对象,导致未预知的bug。
scss 复制代码
import copy

original_list = [1, 2, [3, 4]]
copied_list = copy.copy(original_list)

copied_list[2].append(5)
print(original_list)  # 原始列表也被修改了:[1, 2, [3, 4, 5]]

基本类型和引用类型

在编程中,通常将数据类型分为两种主要类别:基本类型(Primitive Types)和引用类型(Reference Types)。

  • 基本类型是指存储单个值的简单数据类型,它们通常由编程语言直接支持,并在内存中按值进行存储。常见的基本类型包括整数(integers)、浮点数(floats)、布尔值(booleans)和字符(characters)等。在Python中,整数、浮点数、布尔值、字符串等都属于基本类型。
  • 引用类型是指存储引用(指针)而不是实际数据值的数据类型。引用类型的变量存储的是指向内存中实际数据的引用,而不是数据本身。常见的引用类型包括列表(lists)、字典(dictionaries)、集合(sets)、对象(objects)等。在Python中,列表、字典、集合等属于引用类型。

在Python中,与基本类型不同,引用类型的变量存储的是对象的引用,而不是对象本身。这意味着当你将一个引用类型的变量赋值给另一个变量时,实际上是将引用复制给了新的变量,两个变量指向的是同一个对象。这一点在传递参数和修改对象时尤为重要,因为它影响了对象在内存中的共享和修改行为。

浅拷贝和深拷贝

  • 深浅拷贝都会创造一个新的对象。区别是:

    • 浅拷贝只复制了对象的顶层结构,而不会递归地复制对象中包含的子对象。
    • 深拷贝会创建原始对象及其所有子对象的完全独立副本.
  • 举个栗子:

    • 引用类型对象就像文件的桌面快捷方式。浅拷贝就是只对桌面快捷方式进行拷贝,不拷贝原文件。深拷贝对象会对桌面快捷方式和原文件一起拷贝。

应用场景

  • 当需要对原对象进行拷贝时,何时选择浅拷贝、何时选择深拷贝?

    • 原对象所有属性和子对象只有基本类型,可以只使用浅拷贝
    • 原数据所有属性和子对象含有引用类型,使用深拷贝
  • 目的是为了确保新旧对象相互独立,避免未知bug。

ini 复制代码
class Person:
    def __init__(self, name, address):
        self.name = name
        self.address = address

person1 = Person("Alice", ["123 Main St", "City"])
person2 = person1

# 原对象属性只有基本类型,使用浅拷贝
import copy
person3 = copy.copy(person1)

# 原数据属性含有引用类型,使用深拷贝
person4 = copy.deepcopy(person1)

实现方式

  • python深拷贝的实现方式就一种(如果还有其他方式欢迎在评论区补充),浅拷贝实现方式则比较多。可以只记住深拷贝,用排查法确认浅拷贝。当然也可以在不确认时,写个demo验证一下。

浅拷贝

  1. 使用copy.copy()函数
scss 复制代码
import copy
original_list = [1, 2, [3, 4]]
copied_list = copy.copy(original_list)
original_list[-1].append(1)
print(original_list)  # [1, 2, [3, 4, 1]]
print(copied_list)  # [1, 2, [3, 4, 1]]
  1. 切片操作:对于列表、元组等序列类型的对象,可以使用切片操作符来创建一个浅拷贝。
scss 复制代码
original_list = [1, 2, [3, 4]]
copied_list = original_list[:]
original_list[-1].append(1)
print(original_list)  # [1, 2, [3, 4, 1]]
print(copied_list)  # [1, 2, [3, 4, 1]]
  1. 构造函数

    1. 列表(list):list()构造函数可以用来创建一个与原列表相同的新列表。
    2. 元组(tuple):tuple()构造函数可以用来创建一个与原元组相同的新元组。
    3. 字典(dictionary):dict()构造函数可以用来创建一个与原字典相同的新字典。
scss 复制代码
original_list = [1, 2, [3, 4]]
copied_list = list(original_list)
original_list[-1].append(1)
print(original_list)  # [1, 2, [3, 4, 1]]
print(copied_list)  # [1, 2, [3, 4, 1]]

original_list = [1, 2, [3, 4]]
copied_list = tuple(original_list)
original_list[-1].append(1)
print(original_list)  # [1, 2, [3, 4, 1]]
print(copied_list)  # (1, 2, [3, 4, 1])

original_list = {1:1, 2:2, 3:[3, 4]}
copied_list = dict(original_list)
original_list[3].append(1)
print(original_list)  # {1: 1, 2: 2, 3: [3, 4, 1]}
print(copied_list)  # {1: 1, 2: 2, 3: [3, 4, 1]}
  • __copy__魔法方法
python 复制代码
import copy

class MyClass:
    def __init__(self, value):
        self.value = value

    def __copy__(self):
        # 创建一个新的实例并将属性值复制过去
        new_instance = type(self)(self.value)
        return new_instance

obj1 = MyClass(10)
obj2 = copy.copy(obj1)  # 调用 __copy__() 方法创建浅拷贝
print(obj2.value)  # 输出:10

深拷贝

scss 复制代码
import copy

original_list = [1, 2, [3, 4]]
copied_list = copy.deepcopy(original_list)
original_list[-1].append(1)
print(original_list)  # [1, 2, [3, 4, 1]]
print(copied_list)  # [1, 2, [3, 4]]

结束语

  • 浅拷贝错误使用是导致意外共享对象引用的一种原因。除此之外:

    • 参数传递:当将可变对象作为参数传递给函数时,函数内对该对象的修改可能会影响到原始对象。
    • 全局变量: 如果在一个模块中定义了一个全局变量,并且其他模块也引用了这个全局变量,那么对这个全局变量的修改会影响到所有引用它的模块。
相关推荐
海绵宝宝转agent4 小时前
learn-claude-code第1-5章开源学习笔记分享
人工智能·笔记·python·学习
小奇不哭4 小时前
ROS2 单节点,RGB-D 深度相机局部路径提取 + B 样条平滑路径 + YOLOv8 障碍物检测 + 深度测距,用于移动机器人视觉局部循迹。
python·yolov8·ros2·cv2·路径探索循迹
小小龙学IT5 小时前
Python scikit-learn 机器学习库深度解析
python·机器学习·scikit-learn
2601_962071575 小时前
类变量和全局变量的查找路径有什么区别?
开发语言·python
卷无止境6 小时前
独立开发者的"富矿地带":哪些垂直领域值得你押注一辈子?
后端·python
Java后端的Ai之路6 小时前
Python进阶探索29_eval内置函数
开发语言·python·探索·eval·内置函数
计算机毕业编程指导师6 小时前
计算机毕设选题推荐:基于Hadoop与Spark的Steam游戏数据分析系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习 深度学习
大数据·hadoop·python·计算机·spark·毕业设计·steam游戏
计算机毕业编程指导师6 小时前
【计算机毕设选题推荐】基于Hadoop+Spark的白鹿抖音评论大数据分析与可视化系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习 深度学习
大数据·hadoop·python·计算机·spark·毕业设计·抖音评论
Zootopia6267 小时前
飞行力学知识梳理1|飞行性能与稳定性
人工智能·python·算法·机器学习·无人机·学习方法·信息与通信
Y3815326627 小时前
搜索 API 延迟优化:并发数、超时与超时的真实代价
python·搜索引擎