本文共 2915 字,大约阅读时间需要 9 分钟。
'==' VS 'is'
等于(==) 和 is 是 Python 中对象比较常用的两种方式。
'==' 操作符比较对象之间的值是否相等:
a == b # 表示比较变量 a 和 b 所指向的值是否相等。
'is' 操作符比较的是对象的身份标识是否相等, 即它们是否是同一个对象, 是否指向同一个内存地址。
在 Python 中, 每个对象的身份标识, 都能通过函数 id(object) 获得。 因此, 'is' 操作符, 相当于比较对象之间的 ID 是否相等
a1 = 10b1 = 10print('a1 == b1:',a1 == b1)print('id(a1):',id(a1))print('id(b1):',id(b1))print('a1 is b1:',a1 is b1)a2 = 257b2 = 257print('a2 == b2:',a2 == b2)print('id(a2):',id(a2))print('id(b2):',id(b2))print('a2 is b2:',a2 is b2)
'==' VS 'is'.png
对于整型数字来说, 以上 a1 is b1 为 True 的结论, 只适用于 -5 到 256 范围内的数字。
事实上, 出于对性能优化的考虑, Python [CPython(Python 的 C 实现)]内部会对 -5 到 256 的整型维持一个数组, 起到一个缓存的作用。 这样, 每次你试图创建一个 -5 到 256 范围内的整型数字时, Python 都会从这个数组中返回相对应的引用, 而不是重新开辟一块新的内存空间,这样a1 is b1: True。如果整型数字超过了这个范围, 比如上述例子中的 257, Python 则会为两个 257 开辟两块内存区域, 因此 a 和 b 的 ID 不一样,这样a2 is b2: False。
'is' 的速度效率 通常要优于 '==' ,因为 'is' 操作符不能被重载, Python 就不需要去寻找, 程序中是否有其他地方重载了比较操作符。执行比较操作符 'is' , 就仅仅是比较两个变量的 ID 而已。 '==' 操作符却不同,执行 a == b 相当于是去执行 a.__eq__(b) , Python大部分的数据类型都会去重载 __eq__ 这个函数。
t1 = (1, 2, [3, 4])t2 = (1, 2, [3, 4])print('t1 == t2:',t1 == t2)t1[-1].append(5)print('t1 == t2:',t1 == t2)
'=='.png
我们知道元组是不可变的, 但元组可以嵌套, 它里面的元素可以是列表类型, 列表是可变的, 所以如果我们修改了元组中的某个可变元素, 那么元组本身也就改变了。
浅拷贝和深度拷贝
浅拷贝(shallow copy) 和深度拷贝(deep copy)
浅拷贝
浅拷贝指重新分配一块内存, 创建一个新的对象, 里面的元素是原对象中子对象的引用。
Python 中可以用 copy.copy() 来实现对象的浅拷贝,适用于任何数据类型。如果原对象中的元素不可变, 那倒无问题;但如果元素可变, 浅拷贝通常会带来一些副作用:
'''对 l1 执行浅拷贝, 赋予 l2。浅拷贝里的元素是对原对象元素的引用, 因此 l2 中的元素和 l1 指向同一个列表和元组对象,但l1和l2本身是不同对象。'''l1 = [[1, 2], (30, 40)]l2 = list(l1) l1.append(100) 'l1和l2本身是不同对象, l1 的列表新增元素 100而l2不会''l1 中的第一个列表新增元素 3,因为 l2 是 l1 的浅拷贝,l2 中的第一个元素和 l1 中的第一个元素, 共同指向同一个列表,l2 中的第一个列表也会相对应的新增元素 3'l1[0].append(3) print('l1:',l1)print('l2:',l2) '元组是不可变的,这里对 l1 中的第二个元组拼接, 然后重新创建了一个新元组作为 l1 中的第二个元素,与l2的第二个元素不再是用一个对象的引用,操作后 l2 不变, l1 发生改变'l1[1] += (50, 60)print('l1:',l1)print('l2:',l2)
浅拷贝副作用.png
深度拷贝
深度拷贝, 是指重新分配一块内存, 创建一个新的对象, 并且将原对象中的元素, 以递归的方式, 通过创建新的子对象拷贝到新对象中。 因此, 新对象和原对象没有任何关联。
Python 中以 copy.deepcopy() 来实现对象的深度拷贝。
import copyl1 = [[1, 2], (30, 40)]l2 = copy.deepcopy(l1)l1.append(100)l1[0].append(3)print('l1:',l1)print('l2:',l2)
深度拷贝.png
需要注意的是,如果被拷贝对象中存在指向自身的引用, 那么程序很容易陷入无限循环:
import copyx = [1]x.append(x)print('x:',x)y = copy.deepcopy(x)print('y:',y)
被拷贝对象中存在指向自身的引用.png
为什么深度拷贝 x 到 y 后, 程序并没有出现 stack overflow?
深度拷贝函数 deepcopy 中会维护⼀个字典, 记录已经拷⻉的对象与其 ID。 拷贝过程中, 如果字典里已经存储了将要拷贝的对象, 则会从字典直接返回。
源码地址:
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 # 如果字典里已经存储了将要拷贝的对象,则直接返回 ...
如果使用 x == y 比较上述 y = copy.deepcopy(x) 的 y 与x
RecursionError
x==y内部执行是会递归遍历列表x和y中每一个元素的值,由于x和y是无限嵌套的,因此会stack overflow报错
参考资料:
极客时间 Python核心技术与实战学习
Python核心技术与实战(极客时间)链接:
GitHub链接:
知乎个人首页:
简书个人首页:
个人Blog:
欢迎大家来一起交流学习
转载地址:https://blog.csdn.net/leacock1991/article/details/101490303 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!