Python类实例变量和类变量

33

我不太理解Python中类/实例变量是如何工作的。我不明白为什么当我尝试这段代码时,列表变量似乎是一个类变量。

class testClass():
    list = []
    def __init__(self):
        self.list.append('thing')

p = testClass()
print p.list

f = testClass()
print f.list

输出:

['thing']
['thing', 'thing']

而且当我这样做时,它似乎是一个实例变量

class testClass():
    def __init__(self):
        self.list = []
        self.list.append('thing')

p = testClass()
print p.list

f = testClass()
print f.list

输出:

['thing']
['thing']

1
如果我理解正确的话,您是在询问关于Python语法特定设计决策背后的原因。对于这样的问题,最好的答案要么是“因为它符合该语言的精神”,要么更直接地说:“去问它的创建者”。 - Xion
好的,变量默认情况下是类变量,这就是我的困惑所在,这应该是我的问题,变量是否默认为类变量。 - jonathan topf
5
@jonathantopf 不是的,默认情况下变量不是类变量。在Python中,你不需要声明变量。实际上,它们并不是真正的变量(Python是一种“无变量”语言),只是名称。你也不需要声明或赋值名称,而是将它们“绑定”到对象上。运行时每个对象都有一个名称到对象的字典。即使两个对象属于同一类,它们的字典也可以非常不同。 - rodrigo
在你的第一个例子中,你创建了一个真正的共享资源,供MyClass类的所有成员使用。然后,你继续将共享特性的内容私有化到MyClass类的每个成员中。 - yurisich
1
可能是重复的问题:如何避免Python类数据在实例之间共享? - Martijn Pieters
在JavaScript中,由于略有不同的原因,会出现相同的问题。作为一名长期的JavaScript程序员,当我开始学习Python时,我并不感到惊讶。实际上,我就是这样找到了这篇文章。 - Tomáš Zato
4个回答

55
这是因为Python解析带有“.”的名称的方式。当你写self.list时,Python运行时会首先尝试在实例对象中查找list名称,并且如果在那里找不到,就会在类实例中查找。

让我们逐步深入研究它。

self.list.append(1)
  1. 对象 self 中是否有名称为 list 的列表?
    • 是:使用它!结束。
    • 否:继续第2步。
  2. 对象 self 的类实例中是否有名称为 list 的列表?
    • 是:使用它!结束。
    • 否:错误!

但当您绑定一个名称时,情况就不同了:

self.list = []
  1. 对象 self 中是否有一个名为 list 的列表?
    • 是:覆盖它!
    • 否:绑定它!

因此,这总是一个实例变量。

你的第一个示例在类实例中创建了一个 list,因为这是此时的活动作用域(没有出现 self)。但你的第二个示例明确在 self 的作用域中创建了一个 list

更有趣的例子是:

class testClass():
    list = ['foo']
    def __init__(self):
        self.list = []
        self.list.append('thing')

x = testClass()
print x.list
print testClass.list
del x.list
print x.list
那将打印:
['thing']
['foo']
['foo']
删除实例名称后,类名称通过self引用变得可见。

好的,我明白了。那么,有没有一种方法可以在__init__函数之外定义实例变量?我刚刚尝试在__init__之外使用self.list[],这是有道理的,但是有没有其他方法呢? - jonathan topf
1
实例变量只能在有实例的情况下创建。它们必须在调用实例方法的方法中创建。您的另一个选择是重载__new__并在那里创建它们,但这真的很丑陋,因为这不是__new__的真正用途,而且与在__init__中创建它们没有什么区别。请问,您认为您需要这种能力吗?您可以创建一个元类,重载__new__以查看类的字典并将其复制到实例字典中。但那真的很丑陋。 - Omnifarious
我明白了你的意思,这很有道理。虽然我不太喜欢问那样的问题,但有时候这是确认自己想法是否靠谱的最好方式,现在我知道了! - jonathan topf
@jonathantopf 你可以在全局作用域中执行 p.list = [],而不需要在任何函数内部,它会正常工作。 - rodrigo
@jonathantopf: 是的,你所尝试做的是违反 Python 工作方式并试图将其强行转换为 Java 模型。请记住,Python 基本上没有“声明”。它全部都是可执行代码。class 结构只是语法糖,用于对 class 实例成员进行一堆赋值。最接近声明的东西是新式类的类__slots__ 变量的赋值。 - Omnifarious
我怎样才能知道一个属性是实例属性还是类属性?这意味着Python是基于原型的,就像Javascript一样吗? - Chao

8

Python在查找名称方面有趣的规则。如果你真的想挑战自己的思维,试试这段代码:

class testClass():
    l = []
    def __init__(self):
        self.l = ['fred']

这将为每个实例提供一个名为l的变量,该变量掩盖了类变量l。如果您执行self.__class__.l,仍然可以访问类变量。
我的理解是...每当您执行instance.variable(即使是方法名称,它们只是值为函数的变量),它都会在实例的字典中查找。如果在那里找不到它,则尝试在实例的类字典中查找。这仅适用于变量“读取”。如果正在分配变量,则始终会在实例字典中创建新条目。

这些麻烦的复杂情况!这个答案真的很有帮助,因为“读取与写入”的情况让我头疼。 - ankush981

4
在你的第一个例子中,list是该类的属性,被所有实例共享。这意味着即使没有testClass类型的对象,你也可以访问它:
>>> class testClass():
...     list = []
...     def __init__(self):
...             self.list.append("thing")
... 
>>> testClass.list
[]
>>> testClass.list.append(1)
>>> testClass.list
[1]

但是所有对象都与类和彼此共享list属性:

>>> testObject = testClass()
>>> testObject.list
[1, 'thing']
>>> testClass.list
[1, 'thing']
>>> 
>>> testObject2 = testClass()
>>> testClass.list
[1, 'thing', 'thing']
>>> testObject2.list
[1, 'thing', 'thing']

0
当您实例化一个类时,__init__方法会自动执行。
在第一种情况下,您的列表是一个类属性,并且由所有实例共享。您得到了两个“thing”,因为您在实例化p时添加了一个,“f”实例化时添加了另一个(第一个已在第一次调用时添加)。

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