关于list:python类中的属性是否共享?

Are the attributes in a Python class shared or not?

本问题已经有最佳答案,请猛点这里访问。

以下代码使我很麻烦:

1
2
3
4
5
6
7
8
9
10
11
12
class mytest:
    name="test1"
    tricks=list()
    def __init__(self,name):
        self.name=name
        #self.tricks=[name]
        self.tricks.append(name)

t1=mytest("hello world")
t2=mytest("bye world")
print t1.name,t2.name
print t1.tricks,t2.tricks

输出是:

1
2
hello world bye world
['hello world', 'bye world'] ['hello world', 'bye world']

这意味着列表tricks由两个实例t1和t2共享,这在https://docs.python.org/3/tutorial/classes.html的第9.3.5节中进行了解释。

但是,如果我执行以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class mytest:
    name="test1"
    tricks=list()
    def __init__(self,name):
        self.name=name
        self.tricks=[name]
        self.tricks.append(name)

t1=mytest("hello world")
t2=mytest("bye world")
x=t1.tricks
if type(x) is list:
    print 'a list'
elif type(x) is tuple:
    print 'a tuple'
else:
    print 'neither a tuple or a list'
print t1.name,t2.name
print t1.tricks,t2.tricks

输出如下:

1
2
3
a list
hello world bye world
['hello world', 'hello world'] ['bye world', 'bye world']

现在,清单tricks似乎不再由这两个实例T1和T2共享。我的问题是,这里的机械师是什么?


区别在于,在第二个示例中,您将创建一个新的列表self.tricks,作为对象的属性:

1
2
3
4
def __init__(self,name):
    self.name=name
    self.tricks=[name]    # <-- this is creating the new attribute for the object
    self.tricks.append(name)

第一个例子是因为python解析名称的方式:如果在对象中找不到self.tricks(因为它没有被创建),那么它会尝试将其作为类的成员来查找。既然有诀窍,那么你就可以使用它。

如果在第二个示例中尝试使用mytest.tricks,您可能会明白:

1
2
3
4
def __init__(self,name):
    self.name=name
    mytest.tricks=[name]    # <-- this is accesing the class attribute instead
    self.tricks.append(name)

这将输出您实际期望的结果。


在第一个示例中,类mytest有一个tricks成员,由其所有实例共享:

1
2
3
4
5
6
class mytest:
    name ="test1"
    tricks = list()
    def __init__(self,name):
        ...
        self.tricks.append(name)

然而,在第二个示例中,mytest实例另外还有一个tricks成员:

1
2
3
4
5
6
7
class mytest:
    name ="test1"
    tricks = list()
    def __init__(self,name):
        ...
        self.tricks = [name]
        ...

self.tricks = [name]语句将名为tricks的属性赋予selfmytest实例。该类仍然有一个通用的tricks成员。

当调用instance.tricks时,python首先在instance.__dict__中查找tricks成员。如果找不到,它会在type(instance).__dict__中查找tricks成员。

因此,第一个示例的实例没有tricks属性,但python将为您提供它们都共享的mytest.tricks。另一方面,第二个示例的实例确实有自己的tricks属性,而python将返回这个属性。


第一种情况创建类变量,第二种情况创建实例变量。

在引用self.foo时,python首先在实例的命名空间字典中检查foo元素,然后在类的命名空间字典中检查foo元素。

在第一种情况下,由于您创建了一个名为tricks的具有可变类型(列表)的类变量,并且没有在方法上重新指定它,所以对该列表的修改可用于类的每个实例。

在第二种情况下,除了用相同名称的实例变量隐藏类变量外,其他情况都是相同的,因此从那时起,对self.tricks的所有引用都引用实例变量而不是类变量。

第二个案例说明:

1
2
3
4
5
6
7
8
9
mytest.__dict__ = {
    'name': 'test1',
    'tricks': [],  # this doesn't get updated
}

t1.__dict__ = {
    'name': 'some_passed_name'
    'tricks': ['some_passed_name'],
}

在第一种情况下,您没有在对象范围上创建tricks属性,所以python使用了类中的属性;在第二种情况下,您创建了一个新列表并将其与对象本身关联,所以python使用了这个属性。

要获得更深入的解释,请看一下:python类属性:一个非常全面的指南


在这个问题上有一点需要注意。

当您传入名称并将其附加到现有的共享tricks列表时,正如您看到的,它是由所有值共享的,因为它就是该列表。

但是,当您在第二个示例中执行self.tricks=[name]时,您正在删除self.tricks的对象实例,并将其替换为列表[name]

这类似于父类和子类;如果子类没有为现有函数提供不同的定义,则调用父类的函数。但是如果你这样做,它会调用孩子的函数。


在这两种情况下,您都将self.name替换为新值。

在第一种情况下,你要改变self.tricks列表,改变一个列表并不能取代它。所以在整个执行过程中,您只有一个列表,它是可变的。

在第二种情况下,行self.tricks=[name]正在更改列表,创建一个新的列表对象。

你可以很容易地反省这一点,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class mytest:
    name ="test1"
    tricks = list()
    print("tricks id during class definition is: {}".format(id(tricks)))

    def __init__(self, name):
        self.name = name
        print("self.tricks id at the beginning of __init__ is: {}".format(id(self.tricks)))
        self.tricks = [name]
        print("self.tricks id after being replaced is: {}".format(id(self.tricks)))
        print("but mytest.tricks id is still: {}".format(id(mytest.tricks)))
        self.tricks.append(name)

t1=mytest("hello world")
t2=mytest("bye world")

给:

1
2
3
4
5
6
7
tricks id during class definition is: 140547174832328
self.tricks id at the beginning of __init__ is: 140547174832328
self.tricks id after being replaced is: 140547174831432
but mytest.tricks id is still: 140547174832328
self.tricks id at the beginning of __init__ is: 140547174832328
self.tricks id after being replaced is: 140547174830600
but mytest.tricks id is still: 140547174832328