Python笔记005-神奇的+=
Python笔记005-神奇的+=
以下是我学习《流畅的Python》后的个人笔记,现在拿出来和大家共享,希望能帮到各位Python学习者。
首次发表于: 微信公众号:科技老丁哥,ID: TechDing,敬请关注。
本篇主要知识点:
-
序列的+=操作其本质上是类实现了
__iadd__
方法,而*=运算是实现了__imul__
方法,这种运算叫做“就地修改”。 -
对可变序列进行+=或*=运算非常方便快捷,但对不可变序列进行进行这些操作却有些问题,特别是多次频繁的增量运算,会极大降低运算效率。
-
不要把可变序列放到元组里,要不然对里面的可变序列进行操作会得到意想不到的结果。
1. 序列的就地修改
在Python中,常见的增量赋值运算符有+=和*=,这两者的行为方式一样,所以可以用+=来做演示。
一个实例能够进行+=运算,是因为该实例的类实现了__iadd__
方法,同理,能够进行*=运算,是因为实现了__imul__
方法,这两种方法都叫做“就地修改”方法。
就地修改是指,eg,a+=b
会直接在原来的变量a所在内存空间上进行修改赋值,而不像a=a+b
一样,先计算出a+b的值,然后在赋值给一个新的变量a,这个新变量a虽然名称一致,但是内存空间和原来的a不一样。
对于可变序列,一般都实现了__iadd__
方法,即都支持+=操作,但不可变序列却没有实现该方法,对不可变序列进行+=操作,本质上是进行a=a+b
一样的操作。如下代码可见一斑。
# 可变序列使用就地运算法
alist=[1,2,3]
print(id(alist)) # 查看变量alist的内存地址 # 2258694188232
alist *=2 # 复制两份
print(alist) # [1, 2, 3, 1, 2, 3]
print(id(alist)) # 2258694188232 #内存地址一样
# 不可变序列无法就地运算
blist=(1,2,3)
print(id(blist)) # 2258694935104
blist*=2
print(blist) # (1, 2, 3, 1, 2, 3)
print(id(blist)) # 2258693897864,此处的内存地址不一样
可以看出,可变序列支持就地修改,会在原来的内存空间上修改变量值,而不可变序列无法就地修改,会新开辟一部分内存空间,用于新变量的赋值。
了解这种特性有什么用了?由于不可变序列每次运算会重新开辟内存空间,完成赋值再追加新元素,所以非常耗时,效率非常低,对少量数据觉察不出来,但大量数据操作情况下,运行速度越来越慢,而且这种情况难以debug,因为可变序列时运算速度正常,所以尽量不要对不可变序列进行这种操作。
有一个例外:str类型是不可变序列,但是字符串拼接操作非常常见,所以Cpython对其进行了特别优化,因此进行增量操作时,不会涉及到复制原字符串到新内存位置的操作。
2. 一个+=的谜题
如果不可变序列中包含有可变序列,并且对可变序列进行增量赋值操作,那么会得到什么?
a=(10,20,[30,40])
a[2]+=[50,60]
# 会直接报错:TypeError: 'tuple' object does not support item assignment
上面的代码会直接报错:TypeError: 'tuple' object does not support item assignment
,说明tuple是不可变序列,不支持修改,但是打印出a会发现:
print(a) # (10, 20, [30, 40, 50, 60])
说明不仅报错,而且还正确执行了增量赋值操作。很神奇吧。
所以这个谜题得到的经验是:不要把可变序列放到元组里,要不然对里面的可变序列进行操作会得到意想不到的结果。
所以上面的代码可以修改为:
b=[10,20,[30,40]]
b[2]+=[50,60]
print(b) # [10, 20, [30, 40, 50, 60]]
运行正常,所以啊,以后多用list,少用tuple。
首次发表于: 微信公众号:科技老丁哥,ID: TechDing,敬请关注。
本文所有代码都已经上传到我的github,欢迎下载
参考资料:
- 《流畅的Python》,Luciano Ramalho (作者) 安道 , 吴珂 (译者)。