内容目录

  • 一、“+”的执行过程
  • 二、“+=”的执行过程
  • 三、典型案例分析
  • 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进行验证:

python可以互相引用吗 python可以+=吗_赋值

关于函数id(),严格来说,函数返回对象的散列值,在CPython中为对象的内存地址。可以简单理解为一个对象的唯一标识,如果两个变量id相同,那么这两个变量一定指向同一个数据。

2.列表

相对于数值,列表属可变序列。“+=”的执行过程就不同于“+”的执行过程,它会直接在a之后添加b的内容。

以此也可得出,这类数据类型的“+=”要比“+”的执行效率高。毕竟“+”涉及到新对象的建立,a的内容先要复制到新空间,再进行“加法”运算。

python可以互相引用吗 python可以+=吗_python可以互相引用吗_02


其实列表的append函数也是原地操作,请自行验证吧。

3.字符串

字符串属于不可变类型,但我们前面提到,它是个特例。在执行“+=”的时候,和列表相同,也会在a之后添加。

python可以互相引用吗 python可以+=吗_python可以互相引用吗_03

因为字符串的拼接在实际情况下经常遇到,Python在创建一个str类型的数据时,会在数据后预留一部分空间。在其后进行拼接操作时,直接补到后面就好。只有当空间不够用的时候,才会“另辟新路”。

这个故事告诉我们,如果你的作用足够大,上头会想办法给你“开绿灯”。

四、有趣案例

这个案例出自《流畅的Python》[巴西]Luciano Ramalho。

尝试分析以下代码的执行结果,在得出答案前,不要下拉。

t = (1,2,[3,4])
t[2] += [5]

大概会有两种思考结果哈:

一,是tuple属于不可变类型,不支持内部元素的赋值,会抛出异常。

二,tuple不可变,但第三个元素是列表,所以可以执行。

其实两种都是正确的。

python可以互相引用吗 python可以+=吗_赋值_04


首先它会执行列表的“+=”操作,这一步没有任何问题。

之后的操作是将得到的列表赋值给元组的第三个元素,这就开始整事了。因为tuple的本身具有不可变性,所以结果会抛出异常,但在这之前,列表的操作其实已经完成了。

读者可借助dis.dis(反汇编函数)对上面例子进行分析。

总结

1、“+”和“+=”的操作区别在于是否进行“原地加法”,即在a变量的位置上进行运算。a变量是否会关联新对象,完全取决于类的__iadd__方法。

2、对不可变序列进行重复拼接,涉及到新对象的产生、复制、追加的操作,执行效率会大大降低。

3、“+=”或“*=”又被称为“增量赋值”,其本身不是一个原子操作(详见最后案例)。