Python中append浅拷贝机制详解

 更新时间:2023年02月07日 10:26:12   作者:程序猿-张益达  
在 Python 中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,Python 并没有拷贝这个对象,而只是拷贝了这个对象的引用,我们称之为浅拷贝,这篇文章主要介绍了Python中append浅拷贝机制,需要的朋友可以参考下

Python中append浅拷贝机制

关于深浅拷贝,最直观的理解就是:

  • 深拷贝:拷贝的程度深,自己新开辟了一块内存,将被拷贝内容全部拷贝过来了;
  • 浅拷贝:拷贝的程度浅,只拷贝原数据的首地址,然后通过原数据的首地址,去获取内容。

这两者的优缺点对比:

  • 深拷贝拷贝程度高,将原数据复制到新的内存空间中。改变拷贝后的内容不影响原数据内容。但是深拷贝耗时长,且占用内存空间。
  • 浅拷贝拷贝程度低,只复制原数据的地址。其实是将副本的地址指向原数据地址。修改副本内容,是通过当前地址指向原数据地址,去修改。所以修改副本内容会影响到原数据内容。但是浅拷贝耗时短,占用内存空间少。

Python内存引用

在C语言中,在声明变量的时候,int a,int b,这两条语句为a,b两个变量分别赋予了两块不同的内存空间,然后赋值的时候再将相应的值存储到对应的存储空间。但是在Python中变量的赋值与C语言是截然不同的,考虑下面的代码:

>>> a = 2
>>> b = 2
>>> id(a)
140736259334576
>>> id(b)
140736259334576

id函数用于获取对象的内存地址,可以发现,变量a和变量b的内存地址竟然一样!

在Python中,先生成对象,变量再对对象进行引用,在这个例子中,1就是对象,然后a再对1进行引用,由于常数是不可变类型,所以1的内存空间是一样的,所以a,b引用的是用一块内存空间。虽然变量名不一样,但是他们引用的对象是相同的。

当然上面举的例子是int类型的,这属于不可变类型。如果是list或者dict呢?来看看下面的例子:

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> id(a)
3145735383560
>>> id(b)
3145735414984

内存地址不一致!基于此,我们步入今天的主题,来看看append方法浅拷贝机制,到底有什么坑!

append方法浅拷贝机制

Python中的append方法是一个常用的方法,可以将一个对象添加到列表末尾。相信大家一定都用过吧?有人去深挖这个函数的用法吗?这里面可以存在一个大坑!

我们来看一个例子:

>>> a = [1, 3, 5, "a"]
>>> b = []
>>> b.append(a)
>>> b
[[1, 3, 5, 'a']]
>>> a.append("aha")
>>> b    # surprise?
[[1, 3, 5, 'a', 'aha']]

思考一下,明明在第三行之后并没有对b操作,那么为什么b会发生改变呢?

回到今天的主题,事实上,append方法是浅拷贝。在Python中,对象赋值实际上是对象的引用,当创建一个对象,然后把它赋值给另一个变量的时候,Python并没有拷贝这个对象,而只是拷贝了这个对象的引用,这就是浅拷贝。

我们逐步来看。首先,b.append(a)就是对a进行了浅拷贝,结果为b=[[1, 3, 5, 'a']],但b[0]与a引用的对象是相同的,这可以通过id函数进行验证:

>>> id(b[0])
3145735177480
>>> id(a)
3145735177480

可见,b[0]与a指向同个内存地址。

下一步,代码执行a.append(0),列表是可变类型,这一步在原地址的列表的末尾添加0,原地址的内容被改变了但是地址没有变(可以将Python中的list理解为链表,所以这个list的地址不会变,这相当于链表的头结点),所以a和b[0]的内容同时被改变了,这就是为什么对a进行append操作b会跟着发生改变。

发生这些的前提是对同一个地址上的内容进行操作,所以影响了指向该地址的所有变量。

所以,在日常使用append函数的时候,就需要将浅拷贝变为深拷贝,有两个解决方案:

  • b.append(list(a))
  • b.append(a[:])

还是上面的例子,来看看这两个方法的结果是不是真的解决了append浅拷贝问题。

>>> a = [1, 3, 5, "a"]
>>> b = []
>>> b.append(list(a))
>>> b
[[1, 3, 5, 'a']]
>>> a.append(0)
>>> a
[1, 3, 5, 'a', 0]
>>> b
[[1, 3, 5, 'a']]
>>> a = [1, 3, 5, "a"]
>>> b = []
>>> b.append(a[:])
>>> b
[[1, 3, 5, 'a']]
>>> a.append(10)
>>> a
[1, 3, 5, 'a', 10]
>>> b
[[1, 3, 5, 'a']]

怎么样,问题是不是解决了!所以日常使用中,一定要避免浅拷贝带来的问题!

这个append的坑,也是我在刷leetcode:77. 组合时注意到的,题解为:

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        def traversal(n, k, start_index):
            if len(path) == k:
                result.append(path[:])   # 精华在这,要解决这里的浅拷贝问题!
                return
            
            for i in range(start_index, n + 1):
                path.append(i)
                traversal(n, k, i + 1)
                path.pop()
        
        path = []
        result = []
        traversal(n, k, 1)
        return result

如果不处理第5行处的浅拷贝问题,会导致运行处下面的结果:

为啥?因为回溯呀,在上面代码的第11行处,一直在向上回溯,所以结果运行出来就变成了空列表!

所以,在刷回溯的题的时候,如果你使用的是Python,一定要注意这一点了!

补充:Python append() 与深拷贝、浅拷贝

深浅拷贝

在 Python 中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,Python 并没有拷贝这个对象,而只是拷贝了这个对象的引用,我们称之为浅拷贝。

在 Python 中,为了使当进行赋值操作时,两个变量互补影响,可以使用 copy 模块中的 deepcopy 方法,称之为深拷贝。

append() 函数

当 list 类型的对象进行 append 操作时,实际上追加的是该对象的引用。

id() 函数:返回对象的唯一标识,可以类比成该对象在内存中的地址。

>>>alist = []
>>> num = [2]
>>> alist.append( num )
>>> id( num ) == id( alist[0] )
True

如上例所示,当 num 发生变化时(前提是 id(num) 不发生变化),alist 的内容随之会发生变化。往往会带来意想不到的后果,想避免这种情况,可以采用深拷贝解决:

alist.append( copy.deepcopy( num ) )

到此这篇关于Python中append浅拷贝机制的文章就介绍到这了,更多相关Python中append浅拷贝内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Python教程通过公共键对不同字典进行排序示例详解

    Python教程通过公共键对不同字典进行排序示例详解

    本篇文章是Python教程基础篇,通过一些示例为大家讲解Python通过公共键对不同字典进行排序的方式,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-09-09
  • Python 中的装饰器实现函数的缓存(场景分析)

    Python 中的装饰器实现函数的缓存(场景分析)

    Python中的装饰器可以用于实现函数的缓存,其原理是在函数执行前,首先判断传入的参数是否在缓存中已经存在对应的计算结果,这篇文章主要介绍了Python 中的装饰器可以用于实现函数的缓存,需要的朋友可以参考下
    2023-02-02
  • Python使用QQ邮箱发送Email的方法实例

    Python使用QQ邮箱发送Email的方法实例

    实际开发过程中使用到邮箱的概率很高,那么如何借助python使用qq邮箱发送邮件呢?正好最近工作遇到这个需求,所以想着把方法分享出来方便大家,所以这篇文章主要介绍了Python使用QQ邮箱发送Email的实现方法,需要的朋友可以参考。
    2017-02-02
  • Django项目创建的图文教程

    Django项目创建的图文教程

    本文主要介绍了Django项目创建的图文教程,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • python模块离线安装方式

    python模块离线安装方式

    这篇文章主要介绍了python模块离线安装方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • python使用difflib实现自动查重

    python使用difflib实现自动查重

    Python中有许多现成的库和工具,可以方便地实现自动查重的功能,其中,difflib库就是一个专门用于比较文件和字符串差异的库,下面我们就来看看如何利用difflib实现自动查重吧
    2023-11-11
  • Python实现求笛卡尔乘积的方法

    Python实现求笛卡尔乘积的方法

    这篇文章主要介绍了Python实现求笛卡尔乘积的方法,结合实例形式分析了Python计算笛卡尔乘积的原理与实现技巧,需要的朋友可以参考下
    2017-09-09
  • Python实现微信表情包炸群功能

    Python实现微信表情包炸群功能

    这篇文章主要介绍了Python实现微信表情包炸群功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • Python ORM框架SQLAlchemy学习笔记之数据添加和事务回滚介绍

    Python ORM框架SQLAlchemy学习笔记之数据添加和事务回滚介绍

    这篇文章主要介绍了Python ORM框架SQLAlchemy学习笔记之数据添加和事务回滚介绍,需要的朋友可以参考下
    2014-06-06
  • Python3与redis交互,保存的是字符串,取出来是bytes类型问题

    Python3与redis交互,保存的是字符串,取出来是bytes类型问题

    这篇文章主要介绍了Python3与redis交互,保存的是字符串,取出来是bytes类型问题,具有很好的参考价值,希望对大家有所帮助,
    2023-09-09

最新评论