zoukankan      html  css  js  c++  java
  • Python

    前言

    • Python 中不存在值传递,一切传递的都是对象的引用,也可以认为是传址
    • 这里会讲三个概念:对象赋值、浅拷贝、深拷贝

    名词解释

    • 变量:存储对象的引用
    • 对象:会被分配一块内存,存储实际的数据,比如字符串、数字、列表
    • 引用:变量指向对象,可以理解为指针

      

    实际的一种应用场景

    • 有一个变量 a,存储了一个值
    • 此时想用另一个变量 b 暂时存储变量 a 的值,以便后续使用
    • 然后继续修改变量 a 的值,但修改的时候并不想同步更改变量 b 的值
    a=1
    b=a
    a=2

      

    对象赋值

    不可变对象的赋值

    a = 1
    b = a
    
    print(a, b)
    
    a += 2
    print(a, b)
    
    print("a id:", id(a))
    print("b id:", id(b))
    
    
    # 输出结果
    1 1
    2 1
    a id: 4564097808
    b id: 4564097776
    • 修改变量 a 的值,不会同步修改变量 b 的值
    • 因为赋值操作 a += 2 后,变量 a 存储的对象引用已经改变了
    • 至于具体的原理,可以看看不可变对象、可变对象的详解 https://www.cnblogs.com/poloyy/p/15073168.html

    可变对象的赋值

    a = [1, 2, 3]
    b = a
    
    print(a, b)
    
    a[1] = 22
    print(a, b)
    
    print("a id:", id(a))
    print("b id:", id(b))
    
    
    # 输出结果
    [1, 2, 3] [1, 2, 3]
    [1, 22, 3] [1, 22, 3]
    a id: 4567683136
    b id: 4567683136
    • 修改 a 变量的值,会同步修改变量 b 的值,这不符合上面的说的实际应用场景
    • 因为变量 a、b 指向的对象是可变对象,所以它们保存的对象引用都是一样的

    拷贝的诞生

    • 那如果要让可变对象也能满足上述实际应用场景,要怎么做呢?
    • 当然就是拷贝
    • 而拷贝又分为浅拷贝、深拷贝,接下来会具体聊一聊两种拷贝的区别

    第一个重点总结

    • 对于不可变对象来说,赋值操作其实就可以满足上面说的实际应用场景
    • 所以!后面要讲的浅拷贝、深拷贝对于不可变对象来说,和赋值操作是一样的效果!
    • 记住!浅拷贝、深拷贝只针对可变对象,即列表、集合、字典!

    copy 模块

    Python 提供了 copy 模块,包含了浅拷贝、深拷贝函数

    from copy import copy, deepcopy
    
    # 浅拷贝
    copy(x)
    
    # 深拷贝
    deepcopy(x)

    浅拷贝

    一句话概括:浅拷贝会创建一个新对象,该新对象存储原始元素的引用

    浅拷贝后的值是相同的

    • 将列表赋值给变量 old_list
    • 通过 copy() 方法对 old_list 变量指向的对象进行浅拷贝,并赋值给新变量 new_list
    • 因为是对象进行拷贝,所以 new_list 和 old_list 存储的值是相同的
    import copy
    
    old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    new_list = copy.copy(old_list)
    
    print("Old list:", old_list)
    print("New list:", new_list)
    
    
    # 输出结果
    Old list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    New list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

    浅拷贝后的会产生一个新的对象

    • 虽然 old_list 和 new_list 存储的值是相同的,但浅拷贝的操作是产生了一个新的对象
    • 所以 old_list 和 new_list 指向的对象并不是同一个
    import copy
    
    old_list = [[1, 2], [3, 4]]
    new_list = copy.copy(old_list)
    
    old_list.append([5, 6])
    
    print("Old list:", old_list, "id is :", id(old_list))
    print("New list:", new_list, "id is :", id(new_list))
    
    
    # 输出结果
    Old list: [[1, 2], [3, 4], [5, 6]] id is : 4366240704
    New list: [[1, 2], [3, 4]] id is : 4366246720

    可以看到内存地址是不同的,所以给 old_list 新增一个元素并不会同步让 new_list 也新增

    原理图

    • 浅拷贝生成了一个新对象,然后赋值给 new_list
    • new_list、old_list 指向的列表对象不是同一个,但值相同
    • 重点:对于列表对象中的元素,浅拷贝产生的新对象只存储原始元素的引用(内存地址),所以两个列表对象的元素的引用都指向同一个内存地址

    那为什么要深拷贝呢?

    修改列表内的不可变对象元素

    上面的栗子是直接添加元素,来看看修改元素会怎么样

    # 不可变元素
    import copy
    
    old_list = [1, 2, "string", (1, 2,)]
    new_list = copy.copy(old_list)
    
    old_list[1] += 22
    old_list[2] += "s"
    old_list[3] += (3,)
    
    print("Old list:", old_list)
    print("New list:", new_list)
    
    
    # 输出结果
    Old list: [1, 24, 'strings', (1, 2, 3)]
    New list: [1, 2, 'string', (1, 2)]

    修改 old_list 的三种不可变对象元素,均不会同步给 new_list

    修改不可变对象的原理图

    修改列表内的可变对象元素

    # 可变元素
    import copy
    
    old_list = [[1, 2], [3, 4]]
    new_list = copy.copy(old_list)
    
    old_list[0][0] += 99
    old_list[1][0] += 97
    
    print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0]))
    print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0]))
    
    
    # 输出结果
    Old list: [[100, 2], [100, 4]] old list id: 4430308096  old list[0] id: 4430302400
    new list: [[100, 2], [100, 4]] new list id: 4430308416  new list[0] id: 4430302400

    从输出结果看到

    • 两个变量保存了不同的对象引用
    • 但是可变对象元素的内存地址仍然是同一个

    修改可变对象的原理图

    总结

    • 修改可变对象是在原始对象上直接操作的
    • 浅拷贝产生的新对象是存储的仍然是原始对象的内存地址
    • 所以修改可变对象的时候,新对象的值也会被同步修改,因为新旧列表对象的元素的引用是指向同一个内存地址
    • 当修改可变对象的时候,不满足一开始说的实际应用场景,所以诞生了深拷贝

    深拷贝

    • 创建一个新对象,且存储的对象引用也是新的
    • 深,意味着会把所有子元素对象也复制生成一个新对象

    栗子一

    # 深拷贝
    old_list = [[1, 2], [3, 4]]
    new_list = copy.deepcopy(old_list)
    
    old_list[0][0] += 99
    old_list[1][0] += 97
    
    print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0]))
    print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0]))
    
    
    # 输出结果
    Old list: [[100, 2], [100, 4]] old list id: 4430308480  old list[0] id: 4430211392
    new list: [[1, 2], [3, 4]] new list id: 4430308096  new list[0] id: 4430308864

    从输出结果看到

    • 两个变量保存了不同的对象引用
    • 可变对象元素(子对象)的内存地址也是不同的

    栗子二

    假设是一个三维列表呢

    # 深拷贝-三维数组
    old_list = [[1, [10, 9]], [3, 4]]
    new_list = copy.deepcopy(old_list)
    
    old_list[0][1][0] += 90
    
    print("Old list:", old_list)
    print("New list:", new_list)
    
    
    # 输出结果
    Old list: [[1, [100, 9]], [3, 4]]
    New list: [[1, [10, 9]], [3, 4]]

    两个变量依旧是独立的

    深拷贝原理图

    浅拷贝的多种实现方式

    https://www.cnblogs.com/poloyy/p/15086511.html

    面试题:浅拷贝、深拷贝的区别

    1. 浅拷贝和深拷贝只有在可变对象才会生效,不可变对象的赋值操作、浅拷贝、深拷贝的效果是一样的
    2. 浅拷贝会将对象复制生成一个新对象,但新对象仍然存储原始对象的引用,当原始对象是可变对象,然后修改它的值时,新旧对象会同时改变
    3. 深拷贝不仅会将对象复制生成一个新对象,且所有原始对象都会复制生成新对象,即使原始对象是可变对象,新对象存储的对象引用也是新的,所以改变旧对象的可变对象时,不会影响新对象
  • 相关阅读:
    记一次生产数据库"意外"重启的经历
    我爬了链家青岛市北3000套二手房得出一个结论
    我用Python实现了一个小说网站雏形
    Linux下安装 Python3
    Lepus搭建企业级数据库慢查询分析平台
    Lepus搭建企业级数据库全方位监控系统
    shell浅谈之九子shell与进程处理
    shell中测试命变量是否已经定义
    getline数据来源你的三种方式
    awk中的system和getline的用法
  • 原文地址:https://www.cnblogs.com/poloyy/p/15084277.html
Copyright © 2011-2022 走看看