面试题-python 浅拷贝和深拷贝(copy模块)

前言

面试的时候经常会问到深拷贝和浅拷贝,那么python的深拷贝和浅拷贝有什么区别呢?

思考题

先来看 2 个简单的案例, 对元素 a/aa 重新赋值一个新的变量 b/bb 后,改变原来 a/aa 的值,看会不会影响新的变量 b/bb 的值

# 1.str a = "hello" b = a a = "world" print('a: {}'.format(a)) print('b: {}'.format(b)) # 2.list aa = [1, 2, 3] bb = aa aa.append(4) print('aa: {}'.format(aa)) print('bb: {}'.format(bb))

运行结果

a: world b: hello aa: [1, 2, 3, 4] bb: [1, 2, 3, 4]

这是个很有趣的事情,字符串重新赋值给b后,改变原来a的值,b不会跟着变。
但是list重新赋值给bb后,改变aa的值,bb的值也跟着变了。
这里有个知识点:在python中,都是将“对象的引用(内存地址)”赋值给变量的。其次,在python中有6个标准数据类型,他们分为可变和不可变两类。

可变和不可变对象

在python中有6个标准数据类型,他们分为可变和不可变两类。

  • 不可变类型:Number(数字)String(字符串)Tuple(元组)
  • 可变类型:List(列表)Dictionary(字典)Set(集合)

可变对象和不可变对象的内存地址可以通过id函数获取

  • 可变对象:可变对象可以在其 id() 保持固定的情况下改变其取值;
  • 不可变对象:具有固定值的对象。不可变对象包括数字、字符串和元组。这样的对象不能被改变。如果必须存储一个不同的值,则必须创建新的对象。
  • id(object):函数用于获取对象的内存地址,函数返回对象的唯一标识符,标识符是一个整数。

字符串和数字都是不可变类型,不同变量赋值一样,通过id获取的内存地址是一样的

# 作者-上海悠悠 QQ交流群:717225969 # blog地址 https://www.cnblogs.com/yoyoketang/ a = "abc" b = "abc" print(id(a)) print(id(b)) print(a is b) c = 100 d = 100 print(id(c)) print(id(d)) print(c is d)

运行结果

1557212603592 1557212603592 True 1561032832 1561032832 True

list、dict 和 set集合是可变类型,虽然值一样,但是id获取的内存地址不一样

# 作者-上海悠悠 QQ交流群:717225969 # blog地址 https://www.cnblogs.com/yoyoketang/ a = {"key": "123"} b = {"key": "123"} print(id(a)) print(id(b)) print(a is b) print(a == b) c = [1, 2, 3] d = [1, 2, 3] print(id(c)) print(id(d)) print(c is d) print(c == d)

运行结果

1638920310144 1638920310216 False True 1638921292360 1638921292680 False True

现在知道了id函数获取内存地址,我们说的深拷贝和浅拷贝是针对可变对象:list、dict 和 set集合

copy模块

python 中的深拷贝和浅拷贝使用 copy 模块

浅拷贝 A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.

上面这段话是官方文档上的描述,有2个含义:

  • 1.浅拷贝会创建一个新的容器对象(compound object)
  • 2.对于对象中的元素,浅拷贝就只会使用原始元素的引用(内存地址)

常见的浅拷贝操作有:

  • 使用切片操作[:]
  • 使用工厂函数(如list/dict/set)
  • copy模块的copy()方法

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

上面这段话是官方文档上的描述,也是有2个含义:

  • 1.深拷贝和浅拷贝一样,都会创建一个新的容器对象(compound object)
  • 2.和浅拷贝的不同点在于,深拷贝对于对象中的元素,深拷贝都会重新生成一个新的对象

浅拷贝

浅拷贝使用 copy 模块的 copy 方法

# 作者-上海悠悠 QQ交流群:717225969 # blog地址 https://www.cnblogs.com/yoyoketang/ import copy a = [1, "hello", [2, 3], {"key": "123"}] b = copy.copy(a) print(id(a)) # 外面容器拷贝了,所以a和b的id不一样 print(id(b)) # a和b容器里面的元素对象id print(id(a[2])) print(id(b[2]))

运行结果

1340977220424 1340977221576 1340977220168 1340977220168

浅拷贝是拷贝了list外面一层的, 创建一个新的容器对象(compound object),所以a和b的id是不一样的
对于容器里面的元素对象,浅拷贝就只会使用原始元素的引用(内存地址),所以可以看到子元素的内存地址还是一样的

如果改变a里面的不可变对象数字和字符串,此时a和b的值就不一样了,但是b的后面没改变的元素还是指向a

# 改变a的 数字和字符串对象 a[0] = 2 # a 和b 的值不一样了 print(a) print(b) # 但是后面的元素还是指的a print(id(a[2])) print(id(b[2]))

运行结果

[2, 'hello', [2, 3], {'key': '123'}] [1, 'hello', [2, 3], {'key': '123'}] 2488134044232 2488134044232

如果改变a里面的可变对象, 把[2, 3]里面的3改成 [2, 4]

# 改变a的 可变对象 [2, 4] a[2][1] = 4 print(a) print(b) print(id(a[2])) print(id(b[2]))

运行结果

[1, 'hello', [2, 4], {'key': '123'}] [1, 'hello', [2, 4], {'key': '123'}] 2385125673544 2385125673544

此时b会随着a的改变而改变,这就是浅拷贝了

深拷贝

浅拷贝使用 copy 模块的 deepcopy 方法

import copy # 作者-上海悠悠 QQ交流群:717225969 # blog地址 https://www.cnblogs.com/yoyoketang/ a = [1, "hello", [2, 3], {"key": "123"}] b = copy.deepcopy(a) print(id(a)) # 外面容器拷贝了,所以a和b的id不一样 print(id(b)) # a和b容器里面的元素对象id print(id(a[2])) print(id(b[2])) # 改变a的 可变对象 [2, 4] a[2][1] = 4 print(a) print(b) print(id(a[2])) print(id(b[2]))

深拷贝和浅拷贝的不同点在于,深拷贝对于对象中的元素,深拷贝都会重新生成一个新的对象。
所以不管a怎么变,都不会影响b的值

赋值

赋值跟浅拷贝 深拷贝是有区别的,可以看下面的示例

# 作者-上海悠悠 QQ交流群:717225969 # blog地址 https://www.cnblogs.com/yoyoketang/ a = [1, "hello", [2, 3], {"key": "123"}] b = a print(id(a)) print(id(b)) # a和b容器里面的元素对象id print(id(a[2])) print(id(b[2])) a[0] = 2 print(a) print(b)

运行结果

1992198687560 1992198687560 1992198687304 1992198687304 [2, 'hello', [2, 3], {'key': '123'}] [2, 'hello', [2, 3], {'key': '123'}]

赋值语句并没有生成新的容器,跟浅拷贝的区别在于外面的容器也是指向的a的内存地址,并没有生成新的容器

参考博客资料https://www.nowcoder.com/discuss/203654?type=2&order=0&pos=1232&page=0
参考博客资料https://copyfuture.com/blogs-details/2020031720252559878eggumgw4iaj7c

2021年第六期《python接口自动化+测试开发》课程,1月9号开学(火热报名中!)

本期上课时间:1月9号-4月18号,每周六、周日晚上20:30-22:30

(0)

相关推荐

  • 大家都是拷贝,凭什么你这么秀?

    之前关于 Python 的作用域.赋值.参数传递,我们接连谈了几篇文章: 全菊变量和菊部变量 关于函数参数传递,80%人都错了 可变对象与不可变对象 今天我们依然要就相关话题继续下去. 首先是上次最后 ...

  • Python中深拷贝与浅拷贝的区别?

    公众号新增加了一个栏目,就是每天给大家解答一道Python常见的面试题,反正每天不贪多,一天一题,正好合适,只希望这个面试栏目,给那些正在准备面试的同学,提供一点点帮助! 小猿会从最基础的面试题开始, ...

  • Python中的引用赋值,深拷贝,浅拷贝

    摘要:Python,引用赋值,深拷贝,浅拷贝 总结一下Python中的变量的引用赋值,深拷贝和浅拷贝,先上结论 赋值引用会直接将内存地址传递过去,此时变量间不仅值相等,内存地址也相等,是同一个对象. ...

  • 可变对象与不可变对象

    前阵子我们聊了下函数的参数传递以及变量赋值的一些内容:关于函数参数传递,80%人都错了. 简单回顾下要点: 1. Python 中的变量不是装有对象的"容器",而是贴在对象上的&q ...

  • 【Python 成长之路】快速理解复制、浅拷贝、深拷贝

    [本文已由 鹏哥贼优秀 授权转载(原创)作者:鹏哥贼优秀] 1. 示例代码 在进行示例代码展示前,我们先理解下什么叫 复制.浅拷贝.深拷贝. [直接赋值]:其实就是对象的引用(别名). [浅拷贝 (c ...

  • Python运维自动化psutil 模块详解(超级详细)

    psutil 模块 参考官方文档:https://pypi.org/project/psutil/ 一.psutil简介 psutil是一个开源且跨平台(http://code.google.com/ ...

  • Python Urllib和urllib2哪个模块好?Python入门

    Python是一门高级的编程语言,它语法简单.清晰,容易入门,可用的包.库.模块有很多,即便是初学者也能够快速实现简单的网络爬虫,那么你知道Python中爬虫模块有哪些吗?我们一起来看看吧. Pyth ...

  • python测试开发django-42.auth模块登陆认证

    前言 在开发一个网站时,经常会用到用户的注册和登陆相关的账号管理功能,auth模块是Django提供的标准权限管理系统,可以提供用户身份认证, 用户组和权限管理. 像用户注册.用户登录.用户认证.注销 ...

  • 面试题-python 什么是迭代器?

    前言 python 里面有 3 大神器:迭代器,生成器,装饰器.在了解迭代器之前,需弄清楚2个概念: 1.什么是迭代 2.什么是可迭代对象 迭代 如果给定一个list或tuple,我们可以通过for循 ...

  • 面试题-python 什么是生成器(generator)?

    前言 在 Python 中,带有 yield 的函数在 Python 中被称之为 generator(生成器). 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器 ...

  • 面试题-python 如何读取一个大于 10G 的txt文件?

    前言 用python 读取一个大于10G 的文件,自己电脑只有8G内存,一运行就报内存溢出:MemoryError python 如何用open函数读取大文件呢? 读取大文件 首先可以自己先制作一个大 ...

  • 面试题-python 什么是闭包(closure)?

    前言 前面学了装饰器,那么闭包和装饰器有什么区别呢? 闭包传递的是变量,而装饰器传递的是函数对象,只是传的参数内容不一样,闭包的概念包含了装饰器,可以说装饰器是闭包的一种,它只是传递函数对象的闭包. ...

  • 面试题-python 垃圾回收机制?

    前言 简历上写着熟悉 python 面试官上来就问:说下python 垃圾回收机制?一盆冷水泼过来,瞬间感觉 python 不香了. Python中,主要通过引用计数(Reference Counti ...