Python中仅操作一个类实例方法修改实例属性,为何其他同时初始化的类实例属性也一同被修改了?
有人碰过类似的问题吗?
代码大致如下:
class A():
def __init__(self):
self.foo = 'test'
@property
def foo(self):
return self._foo
@foo.setter
def foo(self, foo):
self._foo = foo
class B():
def __init__(self):
self.instance1 = A()
self.instance2 = A()
def test(self):
self.instance1.foo = 'testChange'
print(self.instance1.foo)
print(self.instance2.foo)
B().test()
以上代码输出是:
testChange
test
而在实际代码中,仅仅修改一个实例属性,另一个实例属性同步被修改了,即出错输出是:
testChange
testChange
折腾了我一天了,实在无奈
代码检查过,确认不是写错; git diff 过,确认修改没有问题;查看过两个实例的地址,确认过不同;怀疑过虚拟机问题,重启过,没用;怀疑过 pycache 问题,清空过,没用
然而坚信科学不信邪不信神的我,一通调试之后,发现,莫名其妙好了........................
相关环境:
- python 版本: 3.6.7
- 系统环境:ubuntu 18.04 虚拟机环境
Python中仅操作一个类实例方法修改实例属性,为何其他同时初始化的类实例属性也一同被修改了?
这种玄学问题我遇到过多次。开始几次在突然变好之后就没再调查了,以可能是某种不易复现的 bug、我眼花了、智子的干扰等理论搪塞过去了。直到上一次再次遇到,我决定主动出击,在结合了 shell 的历史记录、编辑器的撤销以及使劲的回想后终于发现,是我的代码写错了,后来莫名其妙好了是因为不知道什么时候改了代码。
所以我觉得你一定是写了两行那个赋值语句或者什么的没看清楚。
这个问题很典型,通常是因为你在类定义中,将可变对象(如列表、字典)作为了类属性,而不是在 __init__ 方法中初始化为实例属性。
看个例子就明白了:
class ProblematicClass:
shared_list = [] # 这是一个类属性,所有实例共享
def __init__(self, value):
self.value = value # 这是一个实例属性,每个实例独有
def add_to_list(self, item):
self.shared_list.append(item) # 这里操作的是类属性!
# 创建两个实例
obj1 = ProblematicClass("A")
obj2 = ProblematicClass("B")
obj1.add_to_list("from obj1")
print(obj1.shared_list) # 输出:['from obj1']
print(obj2.shared_list) # 输出:['from obj1'] # 看,obj2的也被改了!
原因:shared_list 是定义在类层面的,它属于类本身。所有通过这个类创建的实例,访问 self.shared_list 时,如果实例自己没有这个属性,Python 就会去类上找这个属性。因此,你通过任何一个实例去修改 shared_list,修改的都是同一个类属性,所有实例看到的结果自然就一样了。
正确做法:在 __init__ 方法中初始化可变对象,这样每个实例都会拥有自己独立的一份。
class CorrectClass:
def __init__(self, value):
self.value = value
self.my_list = [] # 在__init__中初始化,成为实例属性
def add_to_list(self, item):
self.my_list.append(item) # 现在操作的是实例自己的列表
# 创建两个实例
obj1 = CorrectClass("A")
obj2 = CorrectClass("B")
obj1.add_to_list("from obj1")
print(obj1.my_list) # 输出:['from obj1']
print(obj2.my_list) # 输出:[] # obj2的列表不受影响,完美!
一句话总结:检查你的类属性,确保可变对象都在 __init__ 里初始化。
解释器应该不会有这种 bug,猜测可能是一些没注意到的小失误比如两次打印都写成 instance1 了
我一开始就是以为我写错了,所以最开始就排查是不是写错了,把能注释的全注释了,剩下的几乎就是贴出来那段了。但这过程毕竟也是改了代码,没存档,也没法回顾是不是当时检查看错了。。。
最开始就是这么排查的。。。然后一天也没查出有写错的地方 T_T
会不会是编辑器 bug,显示文件内容和实际文件内容不符
有可能^_^ 不纠结了

