Python核心技术与实战——十二|Python的比较与拷贝
我们在前面已经接触到了很多Python对象比较的例子,例如这样的
a =
b =
a == b
或者是将一个对象进行拷贝
l1 = [,,,,]
l2 = l1
l3 = list(l1)
那么现在试一下下面的代码:先创建个列表l1,再把这个列表进行一份拷贝至l2,最后把l1添加一个元素,看l2会发生什么变化?
>>> l1 = [,,,,]
>>> l2 = l1
>>> l1.append()
>>> l2
[, , , , , ]
是不是l2也变了!这里就引申出来一个概念:浅拷贝(shallow copy)和深拷贝(deep copy)
在对拷贝的概念进行分析前,我们先看一看下面的知识:
‘is’VS‘==’
is和==是我们在进行对象比较的时候常用的方法,简单的来说:
== 操作是用来比较两个对象的值是否相等,比如下面的例子,就表示了变量a和b所指向的值是否相等
>>> a =
>>> b =
>>> a == b
True
而is操作是用来判定对象的身份标识是否是相等的,也就是说判定两个对象是否指向同一内存地址。
在python中,每个对象都有一个ID,我们可以通过函数id()来获得
>>>a =
>>>id(a)
因此,is操作就是判定两个对象的id是否相等,我们可以看一看下面的操作
>>> a =
>>> b =
>>> id(a) >>> id(b) >>> a is b
True
过程是这样的:Python会为10这个整形值开辟一块内存,然后变量a和b都指向这块内存区域,所以a和b的id是一样的。但特别要注意的一点:这种情况只适用于-5到256范围内的整形数据。比如下面的例子
>>> a =
>>> b =
>>> a is b
True
>>> a =
>>> b =
>>> a is b
False
处于对性能优化的考虑,Python内部会对-5到256的整型维持一个数组而起到一个缓存的作用。这样每次调用这些数的时候Python就会从这个数组中返回相对应的引用,而不是重新开辟一块内存空间。但是如果超出了这个范围,Python则会为这个数开辟两块不同的区域,所以a和b的id就不一样了。
通常我们的实际工作中用==的次数会比is多得多,因为我们一般更关心两个变量的值而不是他们的存储地址。但是当我们比较一个变量和一个单例(singleton)时,我们常常用is,一个典型的例子就是和None比较
if a is None:
pass
if a is not None:
pass
这里我们要强调一下代码的效率,两种比较操作符中,is是比==高的,因为is操作符不能被重载,这样Python就不需要寻找程序中是否有其他的地方重载了比较操作符,直接去比较两个变量的ID就可以了。
但是==的操作相当于去执行了a.__eq__(b)这个函数,而Python大部分的数据都会去重载__eq__()这个函数,比如对于列表, __eq__函数回去遍历列表终端元素,比较他们的顺序和值是否相等。
说句题外话,对于不可变(immutable)的变量,如果我们之前比较过,是不是就一直不变了呢?答案是否定的,我们看下面的例子
>>> t1 = (1,2,[3,4])
>>> t2 = (1,2,[3,4])
>>> t1 == t2
True
>>> t1[-1].append(5)
>>> t1 == t2
False
元组是不可变的,但是元组可以嵌套使用,这样我们就可以修改元组中的某个元素,那么元组本身就改变了)
浅拷贝和深拷贝
接下来我们就看看Python中的浅拷贝和深拷贝
对于这两个操作,我们先看一看浅拷贝,最常用的浅拷贝的方法,是使用数据类型本身的构造器:
>>> l1 = [,,]
>>> l2 = list(l1)
>>>
>>> l2
[, , ]
>>> l1 == l2
True
>>> l1 is l2
False
这里,l2就是l1的浅拷贝,对于可变的序列,我们还可以通过切片操作完成浅拷贝
>>> l1 = [,,,,]
>>> l2 = l1[:]
>>> l2 == l1
True
>>> l2 is l1
False
我们还可以提供相对应的函数进行浅拷贝
import copy
l1 = [1,2,3,4,5]
l2 = copy.copy(l1)
但是这里有非常重要的一点特别的情况:对于元组,使用tuple()或者切片操作是不会创建一份浅拷贝,反而会返回一个指向相同元组的引用
>>> t1 = (1,2,3,4,5)
>>> t2 = tuple(t1)
>>> t1 == t2
True
>>> t1 is t2
True
元组(1,2,3,4,5)只被创建了一次,t1和t2同时指向这个元组。
所以,浅拷贝是指重新分配一块内存,创建一个新的对象,里面的元素是对源对象中子对象的引用,如果原对象中的元素不可变,倒无所谓,但如果元素可变,浅拷贝会带来一些副作用,尤其需要注意,我们看看下面的例子:
>>> l1 = [[1,2],(30,40)]
>>> l2 = list(l1)
>>> l1.append(100)
>>> l1[0].append(3)
>>> l1
[[1, 2, 3], (30, 40), 100]
>>>
>>> l2
[[1, 2, 3], (30, 40)]
>>>
>>> l1[1] += (50,60)
>>> l1
[[1, 2, 3], (30, 40, 50, 60), 100]
>>>
>>> l2
[[1, 2, 3], (30, 40)]
在上面的例子中,我们先定义了个列表l1,里面有一个列表还有一个元组,然后我们把l1浅拷贝出来一个l2.因为浅拷贝里的元素是对原对象元素的引用,因此l2和l1指向同一个元组和列表对象。接着对l1新添加一个对象100,这个操作是不会对l2产生影响的,因为l2和l1作为整体是两个不同的对象,并不共享内存地址。操作过后l2不变,l1会发生改变。
然后在把l1[0]里添加一个元素3,同样因为l2是l1的浅拷贝,l2中第一个元素和l1中的第一个元素指向同一个列表,因此l2中的第一个列表也会相对应的新增元素3,也就是说l1和l2都会改变。
最后是l1[1] += (50,60),因为元组是不可变的,这里表示l1中的元组进行拼接,然后重新创建了一个新元组作为l1中索引为1的元素。而l2没有重新将新元组进行引用,所以l2不受影响。操作后l2不变l1变化。
从上面的例子我们发现如果在拷贝中使用浅拷贝可能带来的副作用,所以为了避免这种副作用我们可以使用深度拷贝来完整的拷贝一个对象
>>> import copy
>>> l1 = [[1,2],(30,40)]
[[1, 2], (30, 40)]
>>> l2 = copy.deepcopy(l1)
>>> l1,append(100)
>>> l1[0].append(3)
>>> l1
[[1, 2, 3], (30, 40), 100]
>>> l2
[[1, 2], (30, 40)]
可以看出来无论l1怎么变化,l2都不会变化,因为l1和l2相对来说是完全独立没有任何联系的。
但是深度拷贝有些时候也会带来一些问题,如果被拷贝对象啊中存在指向自身的引用,呢么程序就会陷入无限循环
>>> import copy
>>> x = [1]
>>> x.append(x)
>>> x
[1, [...]]
>>> y = copy.deepcopy(x)
>>> y
[1, [...]]
看上面的例子,列表x中有指向自身的引用,所以x是一个无限嵌套的列表,但是我们发现深度拷贝x到y以后,程序中并没出现stack overflow的现象,是因为deepcopy中会维护一个字典用来记录已经拷贝的对象与其ID,在拷贝中如果字典里已经存储了要拷贝的对象,则会从字典直接返回,我们可以看看deepcopy对应的代码
def deepcopy(x, memo=None, _nil=[]):
"""Deep copy operation on arbitrary Python objects. See the module's __doc__ string for more info.
""" if memo is None:
memo = {}
d = id(x) # 查询被拷贝对象x的id
y = memo.get(d, _nil) # 查询字典里是否已经存储了该对象
if y is not _nil:
return y # 如果字典里已经存储了将要拷贝的对象,则直接返回
...
总结
1.比较操作符'=='表示比较对象间的值是否相等,而‘is’表示对象的ID是否相等,及他们是否指向同一块内存地址
2.比较操作符"is"效率由于"==",因为is无法被重载,只是简单的获取对象的ID并进行比较;而"=="操作会递归的遍历对象的所有值,并逐一进行对比
3.浅拷贝中的元素是原对象中子对象的引用,因此如果原对象中的元素是可变的,将其改变后可能影响拷贝后的对象,存在一定的副作用
4.深度拷贝会递归的拷贝原对象中每一个子对象,因此拷贝后的对象和原对象互相独立不影响。此外深度拷贝会维护一个字典用来记录已经拷贝的对象及其ID,可用来提高效率并防止无限递归的发生。
最后留一个思考题,下面的代码输入时什么?为什么?
import copy
x = [1]
x.append(x) y = copy.deepcopy(x)
#下面的输出是什么?
x==y
因为x和y是个无限嵌套的列表,在用“==”进行比较是会进行递归比较,遍历列表中的所有值,而python中为了防止栈崩溃,限制了递归的层数,最后就会爆出错误
RecursionError: maximum recursion depth exceeded in comparison
Python核心技术与实战——十二|Python的比较与拷贝的更多相关文章
- Python核心技术与实战——十六|Python协程
我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...
- Python核心技术与实战——十八|Python并发编程之Asyncio
我们在上一章学习了Python并发编程的一种实现方法——多线程.今天,我们趁热打铁,看看Python并发编程的另一种实现方式——Asyncio.和前面协程的那章不太一样,这节课我们更加注重原理的理解. ...
- Python核心技术与实战——十四|Python中装饰器的使用
我在以前的帖子里讲了装饰器的用法,这里我们来具体讲一讲Python中的装饰器,这里,我们从前面讲的函数,闭包为切入点,引出装饰器的概念.表达和基本使用方法.其次,我们结合一些实际工程中的例子,以便能再 ...
- Python核心技术与实战——十九|一起看看Python全局解释器锁GIL
我们在前面的几节课里讲了Python的并发编程的特性,也了解了多线程编程.事实上,Python的多线程有一个非常重要的话题——GIL(Global Interpreter Lock).我们今天就来讲一 ...
- Python核心技术与实战——十五|深入了解迭代器和生成器
我们在前面应该写过类似的代码 for i in [1,2,3,4,5]: print(i) for in 语句看起来很直观,很便于理解,比起C++或Java早起的 ; i<n;i++) prin ...
- Python核心技术与实战——十|面向对象的案例分析
今天通过面向对象来对照一个案例分析一下,主要模拟敏捷开发过程中的迭代开发流程,巩固面向对象的程序设计思想. 我们从一个最简单的搜索做起,一步步的对其进行优化,首先我们要知道一个搜索引擎的构造:搜索器. ...
- python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL
python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL实战例子:使用pyspider匹配输出带.html结尾的URL:@config(a ...
- Python开发【第二十二篇】:Web框架之Django【进阶】
Python开发[第二十二篇]:Web框架之Django[进阶] 猛击这里:http://www.cnblogs.com/wupeiqi/articles/5246483.html 博客园 首页 ...
- 二十二. Python基础(22)--继承
二十二. Python基础(22)--继承 ● 知识框架 ● 继承关系中self的指向 当一个对象调用一个方法时,这个方法的self形参会指向这个对象 class A: def get(s ...
随机推荐
- 集成ShareSdk一键分享和第三方登录
在Mob官网http://mob.com/注册,创建应用,下载SDK,申请APP_key 根据官网开发指南导入SDK到你的项目中: 在assets/ShareSDk.xml中修改你的APP_key p ...
- Windows监控——性能指标详解(转)
http://blog.csdn.net/yiqin3399/article/details/51730106
- 文件分发服务器 AWS CloudFront(CDN)使用入门-以S3为例 Lebal:Research
引言 在互联网上随意右击一张图片,都可以发现复制图片地址这个选项,这说明他们都有自己的链接(直链),也就是说我们可以通过一个链接本身来访问图片.代码等文件,而不是打开一个网页再选择复制,这就和下载链接 ...
- Activity启动模式分类(一)
standerd 默认模式,每次启动Activity都会创建一个新的Activity实例. 比如:现在有个A Activity,我们在A上面启动B,再然后在B上面启动A,其过程如图所示: single ...
- css换行用省略号代替
css换行用省略号代替,也可以说是长标题的文章可以使用简单的CSS样式实现省略号控制显示. 一般的文字截断(适用于内联与块): .text-overflow{ display:block;/*内联对象 ...
- json,异步加载,时间线
JSON是一种传输数据的格式 JSON.stringify(obj); obj--string JSON.parse(str); string-->obj
- PTA(Basic Level)1011.A+B和C
给定区间 [−231,231] 内的 3 个整数 A.B 和 C,请判断 A+B 是否大于 C. 输入格式: 输入第 1 行给出正整数 T (≤10),是测试用例的个数.随后给出 T 组测试用例,每组 ...
- 洛谷 P1134 阶乘问题 题解
题面 很裸的边取模边乘.注意因为进位的原因模数应该比较大: 另外,这道题是一道标准的分块打表例题(那样的话数据就可以更大了),可以用来练习分块打表: #include<bits/stdc++.h ...
- Luogu P4095 [HEOI2013]Eden的新背包问题
题目 求出从前往后的背包\(f_{i,j}\)和从后往前的背包\(F_{i,j}\). 那么对于询问\((d,e)\),答案就是\(\max\limits_{i=0}^e f_{d-1,i}+F_{d ...
- static 和extern关键字
static是C++中常用的修饰符,它被用来控制变量的存贮方式和可见性.extern "C"是使C++能够调用C写作的库文件的一个手段,如果要对编译器提示使用C的方式来处理函数的话 ...