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中仅操作一个类实例方法修改实例属性,为何其他同时初始化的类实例属性也一同被修改了?

7 回复

这种玄学问题我遇到过多次。开始几次在突然变好之后就没再调查了,以可能是某种不易复现的 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,显示文件内容和实际文件内容不符

有可能^_^ 不纠结了

回到顶部