【问题标题】:Python: __dict__ shows class attribute for the object of the class even when the behavior inside the __init__ mutates the class attributePython:__dict__ 显示类对象的类属性,即使 __init__ 内部的行为改变了类属性
【发布时间】:2020-04-22 19:40:17
【问题描述】:

据我了解,

对于像列表这样的可变对象,如果底层对象支持像列表一样的突变,+=(增强赋值)会导致对象的突变。当然,这不适用于不支持突变的不可变对象,因为__iadd__ 不存在并且__add__ 是被调用的对象。

考虑以下代码:

class TestClass:
    bar = []

    def __init__(self, x):
        self.bar += [x]


f = TestClass(1)
g = TestClass(2)
h = TestClass(3)
print(f.bar)
print(g.bar)
print(TestClass.bar)

现在,我明白为什么所有三个 g、f、h 都会打印出 [1, 2, 3],因为我们正在“改变”类属性。

但是当我尝试通过 print(f.__dict__) 查看对象的命名空间时,我也看到了对象内部的 bar 属性。

问题 1:如果实际发生变异的属性是类属性,为什么要在对象命名空间中创建实例?

其次,“如果”该实例变量是在对象中创建的(如 f),那么 f.bar += 4 应该只影响对象 f,因为 f 中的新实例变量 bar 应该隐藏了类属性。但我看到这样做f.bar+=4 反过来也会更新类属性,这让我完全困惑。

有人可以帮我解决这个问题吗?

【问题讨论】:

  • 不,它没有。它改变了一个实例属性。每次您分配给self.some_attribute 时,它都会分配给一个实例属性。
  • 如果它分配给实例属性,那么为什么 f.bar += 4 稍后也会更新类属性?如果它是在对象的上下文中创建的,那么它应该隐藏类属性吗?
  • 因为类属性从未重新绑定,而是self.bar += [x] 将同一个对象绑定到实例。所以现在你有一个与类属性同名的实例属性,并且两者都绑定到同一个列表对象

标签: python python-3.x


【解决方案1】:

这可能有点微妙。你可以想到

self.bar += [x]

相当于

self.bar = self.bar.__iadd__([x])

所以,self.bar.__iadd__([x]) 改变了列表,本质上是调用 list.extend,然后它返回列表对象。这个列表对象与TestClass.bar 引用的对象相同,被分配给instance 属性

所以现在你有一个与类属性同名的实例属性,并且两者恰好绑定到同一个列表对象

[文档] 中甚至警告过这一点:

对于属性引用的目标,相同的caveat about class and instance attributes 适用于常规作业。

需要注意的是:

【讨论】:

  • 啊,我明白了。它创建了一个别名,现在两者都引用同一个列表对象。知道了。谢谢!
  • @SushrutKaul 并不是真正的别名,它会创建一个与类属性同名的新实例属性(因此它隐藏实例上的类属性)
猜你喜欢
  • 2011-06-20
  • 1970-01-01
  • 2019-04-09
  • 2015-01-05
  • 1970-01-01
  • 2010-12-27
  • 2020-08-01
  • 2018-07-20
相关资源
最近更新 更多