Python中i += x与i = i + x是否等价:深入解析可变与不可变对象
本文深入解析Python中i += x与i = i + x的等价性问题。通过对比整数与列表的操作结果,揭示两者在调用__iadd__与__add__方法时的本质差异。重点阐述可变对象与不可变对象的内存行为,指出对列表使用+=可能引发的潜在Bug,帮助开发者避免常见的引用错误。
前言
近期遇到一个看似简单却蕴含深意的Python题目:表达式 i += x 与 i = i + x 是否等价?若回答“是”,则仅得50分。为何只算对一半?在常规认知中,二者看似无异,特别是在处理整数时表现一致。然而,当涉及列表操作时,情况是否依然如此?
首先观察以下两段代码示例:
代码1
>>> l1 = range(3) >>> l2 = l1 >>> l2 += [3] >>> l1 [0, 1, 2, 3] >>> l2 [0, 1, 2, 3]
代码2
>>> l1 = range(3) >>> l2 = l1 >>> l2 = l2 + [3] >>> l1 [0, 1, 2] >>> l2 [0, 1, 2, 3]
对比代码1与代码2,l2的最终值相同,但l1的值却存在差异。这表明 i += x 与 i = i + x 并非完全等价。那么,究竟在何种条件下等价,何种情况下不等价?
要厘清这一问题,首先需理解两个核心概念:可变对象与不可变对象。
在Python中,所有对象均具备三个通用属性:唯一标识、类型和值。
唯一标识:用于标识对象在内存中的唯一性,该标识在对象创建后保持不变,可通过函数 id() 查看。
类型:决定对象支持的操作,不同类型对象支持的操作各异,例如列表拥有 length 属性而整数没有。对象类型一旦确定便不再改变,可通过函数 type() 获取类型信息。
对象的值与唯一标识不同,并非所有对象的值都固定不变。某些对象可通过特定操作改变其值,这类对象称为可变对象(mutable);反之,值无法改变的对象称为不可变对象(immutable)。
不可变对象(immutable)
对于不可变对象,其值自创建起便恒定不变。对该对象执行的任何操作都会生成一个新的对象。
>>> a = 1 >>> id(a) 32574568 >>> a += 1 >>> id(a) 32574544
整数 “1” 是不可变对象。初始赋值时,变量 a 指向整数对象 1。执行 += 操作后,a 指向新的整数对象 2,而原对象 1 保持不变。常见的不可变对象包括:int、tuple、set、str。

可变对象(mutable)
可变对象的值可通过特定操作动态改变,例如列表对象可通过 append 方法不断添加元素,其值随之变化。当一个可变对象被赋值给两个变量时,它们共享同一实例,指向相同的内存地址。对任一变量进行操作,均会影响另一变量。
>>> x = range(3) >>> y = x >>> id(x) 139726103041232 >>> id(y) 139726103041232 >>> x.append(3) >>> x [0, 1, 2, 3] >>> y [0, 1, 2, 3] >>> id(x) 139726103041232 >>> id(y) 139726103041232

执行 append 操作后,对象的内存地址不变,x 和 y 仍指向原对象,仅其值发生变化。

理解可变与不可变对象后,回归问题本身:+= 与 + 的区别何在?
+= 操作首先尝试调用对象的 __iadd__ 方法;若不存在该方法,则尝试调用 __add__ 方法。接下来分析这两个方法的区别。
__add__ 和 __iadd__ 的区别
- __add__ 方法接收两个参数,返回它们的和,且两个参数的值均不改变。
- __iadd__ 方法同样接收两个参数,但属于原地操作(in-place),即它会改变第一个参数的值。因此,该操作要求对象必须是可变的,不可变对象不具备 __iadd__ 方法。
>>> hasattr(int, '__iadd__') False >>> hasattr(list, '__iadd__') True
显然,整数对象没有 __iadd__ 方法,而列表对象提供了该方法。
>>> l2 += [3] # 代码1:使用__iadd__,l2的值原地修改
代码1中的 += 操作调用的是 __iadd__ 方法,它会原地修改 l2 所指对象的值。

>>> l2 = l2 + [3] # 代码2:调用 __add__,创建了一个新的列表,赋值给了l2
而代码2中的 + 操作调用的是 __add__ 方法,该方法返回一个新对象,原对象保持不变。此时 l1 仍指向原对象,而 l2 指向新对象。

以上即为表达式 i += x 与 i = i + x 的区别。因此,对列表进行 += 操作时可能存在潜在 Bug,因为 l1 会随 l2 的变化而改变,正如函数参数不宜使用可变对象作为默认值一样。
总结
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













