Python 中,类实例可以引用类的属性,就地操作符可以就地修改一个可变对象。那么通过实例引用一个可变类型的类属性,并进行就地操作,会产生什么结果?我开始的推测是类的属性会被改变但不会创建实例的属性,那实际的结果是什么呢?
试一下:
class T:
li = []
t = T()
t.li += 'abc'
print(t.__dict__) # t 的属性中有 li,值为 ['a', 'b', 'c']
print(T.__dict__) # T 的属性中有 li,值为 ['a', 'b', 'c']
果然翻车了,先说下我的思路:
-
t.li
是引用的类属性li
,即t.li
指向的是T.li
的地址; - 对列表的原地操作,只会改变列表的值,但不会改变其内存地址
基于上以上两点的认知,所以我推测不会创建实例 t
的 li
属性。但事实很打脸,为此我查了下官方文档。文档在赋值语句的介绍中指出:
如果该对象为类实例并且属性引用在赋值运算符的两侧都出现,则右侧表达式 a.x 可以访问实例属性或(如果实例属性不存在)类属性。 左侧目标 a.x 将总是设定为实例属性,并在必要时创建该实例属性。 因此,a.x 的两次出现不一定指向相同的属性:如果右侧表达式指向一个类属性,则左侧表达式会创建一个新的实例属性作为赋值的目标:
class Cls: x = 3 # class variable inst = Cls() inst.x = inst.x + 1 # writes inst.x as 4 leaving Cls.x as 3
文档中说的很清楚,那么回到我们上面的测试:
t.li += 'abc'
这条语句可以理解为 t.li = t.li + list('abc')
,这样可以解释为什么会创建 t
实例的属性,但是无法解释类 T
的属性 li
的值为什么会改变。继续查文档,在标准库的 operater
模块中,有关于原地操作符的介绍:
Many operations have an "in-place" version. Listed below are functions providing a more primitive access to in-place operators than the usual syntax does; for example, the statement
x += y
is equivalent tox = operator.iadd(x, y)
.
大概的意思:很多操作符都有一个”就地操作“版本。下列的函数提供了一种比常规语法更原始的方式实现就地操作,比如 x +=y
等效于 x = operator.iadd(x, y)
。
好了,现在研究下 operator.iadd
import operator
li = []
new_li = operator.iadd(li, 'abc')
print(new_li) # ['a', 'b', 'c']
print(li) # ['a', 'b', 'c']
print(new_li == li) # True
print(li is new_li) # True
可以看到原地操作符在操作可变数据类型时,会更新原数据,并且返回值是更新后的原数据。那么 t.li += 'abc'
等效于 t.li = operator.iadd(t.li, 'abc')
,这样,结合赋值语句的特性,就解释了结果。
网友评论