内容目录
- 序
- 一、“+”的执行过程
- 二、“+=”的执行过程
- 三、典型案例分析
- 1.数值
- 2.列表
- 3.字符串
- 四、有趣案例
- 总结
序
在学习过程中,我们被告知以下两种书写方式的效果是一样的:
a = a + b
a += b
然而,此处的效果相同仅是运算结果的相同,并不代表两句代码的执行过程相同。
一、“+”的执行过程
我们知道,Python中一切皆为对象(不是“找”的那个,是“new”的那个)。
“+”运算符怎样执行,取决于“+”之前数据的类型。如果是“1+2”,则会执行整数对象的__add__方法。
如果是“ ‘abc’ + ‘def’ ”,则会调用字符串对象的__add__方法。
其余的“+”也是一样的。
以a = a + b为例,首先计算a+b得到一个新的对象,之后赋值给a,这便是“+”的执行过程。
二、“+=”的执行过程
与“+”号不同,“+=”属于原地操作,即在a所指位置上进行“加法”运算。(“加法”加引号是因为具体操作与数据类型有关,如字符串的“+”是做拼接,数值的“+”是做数学意义上的加法。)
而且,与“+”不同,“+=”调用对象的__iadd__这个特殊方法。只有在没有__iadd__的时候,才会退而求其次用_add__方法代替。此时的+和+=就没有任何区别了。
一般来讲,可变序列都实现了__iadd__,不可变序列中str是例外,其余的类型只实现了__add__。
三、典型案例分析
1.数值
由以上分析可知,数值上的加法“+”和“+=”是完全相同的。都是要产生一个新的对象计算a+b,之后再复制给a。
用内置函数id进行验证:
关于函数id(),严格来说,函数返回对象的散列值,在CPython中为对象的内存地址。可以简单理解为一个对象的唯一标识,如果两个变量id相同,那么这两个变量一定指向同一个数据。
2.列表
相对于数值,列表属可变序列。“+=”的执行过程就不同于“+”的执行过程,它会直接在a之后添加b的内容。
以此也可得出,这类数据类型的“+=”要比“+”的执行效率高。毕竟“+”涉及到新对象的建立,a的内容先要复制到新空间,再进行“加法”运算。
其实列表的append函数也是原地操作,请自行验证吧。
3.字符串
字符串属于不可变类型,但我们前面提到,它是个特例。在执行“+=”的时候,和列表相同,也会在a之后添加。
因为字符串的拼接在实际情况下经常遇到,Python在创建一个str类型的数据时,会在数据后预留一部分空间。在其后进行拼接操作时,直接补到后面就好。只有当空间不够用的时候,才会“另辟新路”。
这个故事告诉我们,如果你的作用足够大,上头会想办法给你“开绿灯”。
四、有趣案例
这个案例出自《流畅的Python》[巴西]Luciano Ramalho。
尝试分析以下代码的执行结果,在得出答案前,不要下拉。
t = (1,2,[3,4])
t[2] += [5]
大概会有两种思考结果哈:
一,是tuple属于不可变类型,不支持内部元素的赋值,会抛出异常。
二,tuple不可变,但第三个元素是列表,所以可以执行。
其实两种都是正确的。
首先它会执行列表的“+=”操作,这一步没有任何问题。
之后的操作是将得到的列表赋值给元组的第三个元素,这就开始整事了。因为tuple的本身具有不可变性,所以结果会抛出异常,但在这之前,列表的操作其实已经完成了。
读者可借助dis.dis(反汇编函数)对上面例子进行分析。
总结
1、“+”和“+=”的操作区别在于是否进行“原地加法”,即在a变量的位置上进行运算。a变量是否会关联新对象,完全取决于类的__iadd__方法。
2、对不可变序列进行重复拼接,涉及到新对象的产生、复制、追加的操作,执行效率会大大降低。
3、“+=”或“*=”又被称为“增量赋值”,其本身不是一个原子操作(详见最后案例)。