Python类和实例变量的复合赋值

7
我一直在尝试理解Python处理类和实例变量的方法。特别是,我发现这个答案非常有帮助。基本上它说,如果你声明了一个类变量,然后你对[instance].property进行赋值,你将会分配到完全不同的变量——一个在与类变量不同的命名空间中。
那么,我考虑过——如果我想让我的类的每个实例都有一个具有某些默认值(比如零)的成员,我应该这样做:
class Foo:
    num = 0

你是指这样吗?或者像这样吗?
class Foo:
    def __init__(self):
        self.num = 0

根据我之前阅读的内容,我认为第二个示例将初始化“right”变量(实例而不是类变量)。然而,我发现第一种方法也完全正常:

class Foo:
    num = 0

bar = Foo()
bar.num += 1 # good, no error here, meaning that bar has an attribute 'num'
bar.num
>>> 1
Foo.num
>>> 0 # yet the class variable is not modified! so what 'num' did I add to just now?

所以,为什么这个有效呢?我哪里理解有误了吗?值得一提的是,我的面向对象编程知识主要来自于C++,因此通过类比或指出其不足之处进行解释可能会更有帮助。
5个回答

5
Personally,我发现Shalabh Chaturvedi的这些文件在这个主题方面非常有用和信息丰富。 bar.num += 1bar.num = bar.num + 1的简写。这会在右侧获取类变量Foo.num并将其分配给实例变量bar.num

1
当我说类/实例“变量”时,我真正意思的是“属性”。 - MattH

2
在以下代码中,num 是一个类成员。
class Foo:
    num = 0

一个等价的C++代码可能是这样的:
struct Foo {
  static int num;
};

int Foo::num = 1;

class Foo:
    def __init__(self):
        self.num = 0

self.num 是实例成员 (selfFoo 的实例)。

在 C++ 中,类似的语句应该是:

struct Foo {
  int num;
};

我相信Python允许你有一个类成员和一个实例成员共享同一个名称(C ++不允许)。所以当你执行bar = Foo()时,barFoo的一个实例,因此使用bar.num += 1,你增加了实例成员。


您的C++等价代码似乎将num的值增加了1! :) - Kylotan

0
bar.num += 1

在'bar'实例上创建一个新的实例变量'num',因为它还不存在(然后将值加1)

一个例子:

class Foo:
  def __init__(self):
    self.num= 1

bar = Foo()
print bar.num

这将打印1

print bar.foo

这会产生一个错误,如预期所述:Traceback (most recent call last): File "", line 1, in AttributeError: Foo实例没有'foo'属性

bar.foo = 5
print bar.foo

现在这将打印 5。

那么在你的例子中会发生什么:bar.num 被解析为 Foo.num,因为只有一个类属性。然后,由于分配了一个值,foo.num实际上被创建。


嗯... bar.foo += 1 也会报错,因此很明显Python不会在包含'+='的表达式中遇到实例变量时创建它们。我的问题是,创建类变量如何导致相应实例变量的初始化? - int3
1
@int3。这与属性解析有关。访问属性时,首先搜索对象本身(在此例中为num),然后搜索对象的类型(在此例中为Foo),然后搜索对象类型的基类及其基类。如果找不到匹配的属性,则会引发AttributeError - MattH
确实,然后实例变量被创建,因为我们给它赋了一个值。我会更新这个例子。 - Stefan De Boey

0

搜索这个问题时,无论是这个StackOverflow问题还是Guido van Rossum的两个(相当旧但仍然有效)幻灯片(12)都排名靠前。 Guido的幻灯片说明了这种行为与Python访问属性的搜索顺序有关(在上面示例中的num情况下)。认为将它们放在一起会很好 :)


-3

我认为你在 Python 中发现了一个 bug。bar.num += 1 应该是一个错误,但实际上它正在创建一个 num 属性在对象 bar 中,与 Foo.num 不同。

这是一个非常奇怪的行为。


@Morgaelyn:不,这不是一个错误,这是预期的行为。请阅读有关Python类的文档http://docs.python.org/tutorial/classes.html。 - MattH
正如MattH所说,这是预期的和有文档记录的行为。因此,不,这不是一个错误。 - krawyoti
现在我明白了。b.num += 1与b.num = b.num + 1是相同的。第一个b.num指的是对象属性并被创建。第二个b.num实际上是Foo.num,值为0。现在有意义了。我仍然认为这是语言设计上的错误。很容易假设b.num在b.num += 1中总是指同一个变量,但实际上它同时指两个不相关的变量。 - Thiago Chaves
不是设计错误,而是实现细节。通常情况下,你不会用实例属性来遮蔽类属性。 - Jürgen A. Erhard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接