Python非直观成员变量行为

Python非直观成员变量行为,python,class,variables,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.

此脚本:

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和STR都被创建为对象变量。

testa
中,变量
a
是类变量,在所有实例之间共享
ta1.a
ta2.a
参考相同的列表

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


有关详细信息,请参阅。

一个是类变量,另一个是实例变量。
类变量在类的所有成员之间共享,实例变量对每个实例都是唯一的。

帮助记住Python中的<代码>类< /C> >语句比任何其他块语句都要接近,如C++语言的情况。

在Python中,
语句包含一组要执行的语句,就像
def
if
while
一样。区别在于解释器对块的处理方式。在流控制块语句(如
if
while
)的情况下,解释器按照流控制语句的含义执行块。在
def
中,解释器保存块并在调用函数对象时执行它

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

为此:

class testa():
    a = []
Python执行块
a=[]
。最后,作用域包含绑定到空列表对象的
a
。这就是你的课程中的内容。不是类的任何特定实例,即类本身

它从
对象
继承了一个不做任何事情的构造函数,因此当您使用
ta1=testa()
实例化该类时,会得到一个空实例。然后,当您请求
ta1.a
时,Python没有找到名为
a
的实例变量(因为
ta1
根本没有实例变量),因此它在类
testa
中查找
a
。这当然是它发现的;但是它会在
testa
的每个实例中找到相同的一个,因此您可以得到您观察到的行为

另一方面,这:

class testb():
    def __init__(self):
        self.a = []
完全不同。在这里,一旦类块被执行,类作用域的内容又是一个名称,但这次它绑定到一个函数。这将成为课程的内容

现在,当您使用
testb()
实例化这个类时,Python会在类中找到
\uuuu init\uuuu
,并使用新实例调用该函数。该函数的执行将在新实例中创建一个
实例变量。因此,
testb()
的每个实例都有自己的变量,您可以观察到行为

带回家消息:Python中的类< /Cux>块是>No>(强)>只是一组声明的包含在该类实例中的东西,不像传统的OO-ISH语言,如C++和java。它是实际执行的代码,用于定义类的内容。这非常方便:您可以使用if语句、函数执行的结果以及在任何其他上下文中使用的任何内容来决定在类中定义什么

注意:为了简单起见,我在前面撒了谎。即使是
testa
的实例也会有一些实例变量,因为默认情况下会为所有实例自动创建一些,但在日常Python中看不到那么多)

testa和testb的定义对我来说似乎是等价的,那么为什么行为会发生如此剧烈的变化呢

Python的行为完全是逻辑的——比java或C++或C语言的行为更为强大,在<>强>在类< /Cult>范围内声明的所有内容属于类。< /强>

在Python中,类在逻辑上是对象的模板,但在物理上不是。最接近指定“实例正好有这组成员”的方法是从
对象继承,将它们列在类'
\uuuuu slots\uuuuuuuuu
中,并在
\uuuuu init\uuuuuu
中初始化它们。但是,Python不会强制您在
\uuuu init\uuuu
中设置所有值,未设置的值不会获得任何默认值(尝试访问它们会导致
属性错误
),您甚至可以稍后(通过
del
)从实例中显式删除属性。
\uuuu init\uuuu
所做的一切都是在每个新实例上运行的(这是设置属性初始值的一种方便而惯用的方法)。
\uuuu\uuuu
所做的就是防止添加其他属性

这似乎不直观,因为它与int和str等其他类型的行为方式不同。由于某些原因,列表在未在init中初始化时创建为类变量,但无论发生什么情况,int和str都创建为对象变量

事实并非如此

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