Python不直观的成员变量行为

时间:2011-09-30 06:14:06

标签: python class variables

这个脚本:

class testa():
    a = []

class testb():
    def __init__(self):
        self.a = []

ta1 = testa(); ta1.a.append(1); ta2 = testa(); ta2.a.append(2)
tb1 = testb(); tb1.a.append(1); tb2 = testb(); tb2.a.append(2)

print ta1.a, ta2.a, tb1.a, tb2.a

生成此输出:

[1, 2] [1, 2] [1] [2]

但我期待

[1] [2] [1] [2]

为什么我错了? testa和testb的定义似乎与我相同,那么为什么行为会发生如此剧烈的变化?!

编辑:这似乎不直观,因为它与int和str等其他类型的行为不同。出于某种原因,未在 init 中初始化时,列表会被创建为类变量,但无论如何都会将int和strs创建为对象变量。

4 个答案:

答案 0 :(得分:7)

testa中,变量a类变量,并在所有实例之间共享。 ta1.ata2.a引用相同的列表。

testb中,变量a对象变量。每个实例都有自己的值。

有关详细信息,请参阅Class and Object Variables

答案 1 :(得分:4)

一个是类变量,另一个是实例变量 类变量在类的所有成员之间共享,实例变量对每个实例都是唯一的 http://docs.python.org/tutorial/classes.html

答案 2 :(得分:3)

有助于记住Python中的class语句比任何其他块语句更接近C ++之类的语言。

在Python中,class语句包含一堆要执行的语句,就像defifwhile一样。区别在于解释器对块的作用。对于像ifwhile这样的流控制块语句,解释器按照流控制语句的含义执行块。在def中,解释器保存块并在调用函数对象时执行它。

对于class块,Python 在新范围内立即执行块,然后在执行完成后使用该范围内剩余的任何内容作为类。

所以对此:

class testa():
    a = []

Python执行块a = []。然后在最后,范围包含绑定到空列表对象的a。这就是中的内容。不是类的任何特定实例,也就是类本身。

它继承了object中的do-nothing构造函数,因此当您使用ta1 = testa()实例化该类时,您将获得一个空实例。然后,当您要求ta1.a时,Python找不到名为a的实例变量(因为ta1根本没有实例变量),因此它在类中查找a testa。这当然是它找到的;但它为testa的每个实例找到相同的一个,因此您得到了您观察到的行为。

另一方面,这个:

class testb():
    def __init__(self):
        self.a = []

完全不同。这里一旦执行了类块,类范围的内容又是一个单独的名称,但这次它是__init__绑定到一个函数。这就成了班上的内容。

现在,当您使用testb()实例化类时,Python会在类中找到__init__并使用新实例调用该函数。该函数的执行在新实例中创建a 实例变量。因此,testb()的每个实例都会获得自己的变量,并且您会看到行为。

带回家消息:Python中的class只是一组包含在该类实例中的事物的声明,与传统的OO-ish语言(如C ++)不同和Java。它是实际执行的实际代码,用于定义类的内容。这非常方便:您可以在类体中使用if语句,函数执行结果以及任何其他上下文中使用的任何内容来决定在类中定义的内容。

注意:我之前为了简单而撒谎。即使testa的实例也会有一些实例变量,因为默认情况下会为所有实例自动创建一些变量,但是你不要在日常的Python中看不到它们。

答案 3 :(得分:0)

  

testa和testb的定义似乎与我相同,那么为什么行为会发生如此剧烈的变化?!

Python的行为是完全合乎逻辑的 - 比Java或C ++或C#的行为更合乎逻辑,因为您在class范围内声明的所有内容都属于该类。

在Python中,类在逻辑上是对象的模板,但它实际上不是物理模板。您最接近指定“实例具有这组成员”的最接近的是继承自object,将其列在“__slots__”类中,并在__init__中初始化它们。但是,Python不会强制您设置__init__中的所有值,您未设置的值不会获得任何默认值(尝试访问它们导致AttributeError)并且您甚至可以在以后显式删除实例中的属性(通过del)。所有__init__所做的就是在每个新实例上运行(这是设置属性初始值的一种方便且惯用的方式)。 __slots__所做的就是阻止添加其他属性。

  

这似乎不直观,因为它与int和str等其他类型的行为不同。出于某种原因,在init中未初始化时,列表被创建为类变量,但无论如何都将int和strs创建为对象变量。

事实并非如此。

如果您在其中放置intstr,它仍然是该类的成员。如果你能以某种方式修改intstr,那么这些更改将反映在每个对象中,就像它们与列表一样,因为查找对象中的属性会在类中找到它。 (粗略地说,属性访问首先检查对象,然后检查类。)

但是,你实际上无法做到。您认为等效测试完全不相同,因为使用intstr的测试会导致对对象属性的赋值,这会隐藏类属性。 ta1.a.append调用列表的方法,这会导致列表的内部更改。对于Python的字符串和整数,这是不可能的。即使您编写类似ta1.n += 1的内容,也会转换为ta.n = ta.n + 1,其中查找属性(并在类中找到),计算总和,然后将其分配回ta.n(赋值)将始终更新对象,除非类“__slots__明确禁止,在这种情况下会引发异常。”

您可以通过直接从类访问该值来简单地证明该值属于该类,例如testa.a