字典和数组作为类变量与实例变量

0 投票
2 回答
1852 浏览
提问于 2025-04-17 01:10

这是一个简单的方法来赚取一些积分。请解释以下内容:

class C:
    a = {}
    b = 0
    c = []

    def __init__(self):
        self.x = {}

    def d(self, k, v):
        self.x[k] = v
        self.a[k] = v;
        self.b = v
        self.c.append(v)

    def out(self, k):
        print(self.x[k], self.a[k], self.b, self.c[0])

c = C()
d = C()
c.d(1, 10)
d.d(1, 20)
c.out(1)  
d.out(1)

将输出以下内容:

10 20 10 10
20 20 20 10

为什么字典、列表和“普通”变量的表现会不一样呢?

补充说明:我原以为这个问题很明显,但让我详细说一下:

我有一个类,里面有三个属性,分别是 a、b 和 c。我创建了这个类的两个实例。然后我调用一个方法来修改每个实例的这些属性。当我检查这些属性时,我发现如果某个属性是字典,那么它在所有实例之间是共享的;而如果是“普通”变量,它的表现就如你所期待的那样,每个实例都有不同的值。

2 个回答

2

a 和 c 是类属性,b 和 x 是实例属性。

你应该阅读并理解 Python:类属性和实例属性的区别

5

首先,[] 不是数组,而是一个列表。这里的问题在于属性的解析和可变变量是如何工作的。我们先来看一下

class Foo(object):
    a = {}
    b = 0
    c = []

这段代码创建了一个类,里面有三个属性——这些属性可以通过类本身来访问(比如 Foo.a),也可以通过类的实例来访问(比如 Foo().a)。属性存储在一个叫 __dict__ 的特殊地方。类和实例都有一个 __dict__(虽然有些特殊情况不适用,但在这里不重要)——在 Foo 的情况下,实例的 __dict__ 在创建时是空的——所以当你使用 Foo().a 时,实际上你是在访问和 Foo.a 一样的对象。

现在,你要添加 __init__ 方法。

class Foo(object):
    # ...

    def __init__(self):
        self.x = {}

这个方法创建了一个属性,不是在类的 __dict__ 中,而是在实例的 __dict__ 中,所以你不能通过 Foo.x 来访问它,只能通过 Foo().x。这也意味着 x 在每个实例中都是一个完全不同的对象,而类属性是所有实例共享的。

接下来你要添加一个修改方法。

class Foo(object):
    # ...

    def mutate(self, key, value):
        self.x[key] = value
        self.a[key] = value
        self.b      = value
        self.c.append(value)

你还记得 self.x = {} 是创建一个实例属性吗?这里的 self.b = value 也是做同样的事情——它完全不影响类属性,而是创建了一个新的属性,这个新属性在实例中覆盖了共享的属性(这就是 Python 中引用的工作方式——赋值只是将名称绑定到一个对象,而不会修改原来名称指向的对象)。

但是你没有重新绑定 self.aself.c——你是在原地修改它们(因为它们是可变的,你可以这样做)——所以实际上你是在修改原来的类属性,这就是为什么你可以在其他实例中观察到变化(因为这些属性是共享的)。而 self.x 的行为不同,因为它不是类属性,而是实例属性。

你还只打印了 self.c 的第一个元素——如果你打印全部内容,你会看到它是 [10, 20]

撰写回答