Python类中的属性是否共享?

时间:2017-07-24 15:44:04

标签: python list class

以下代码让我感到困扰: -

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

输出结果为: -

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节中解释过。

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

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

输出如下: -

a list
hello world bye world
['hello world', 'hello world'] ['bye world', 'bye world']

现在似乎这两个实例t1和t2不再共享列表tricks。 我的问题是,这里的机制是什么?

6 个答案:

答案 0 :(得分:8)

不同之处在于,在第二个示例中,您要创建一个新列表self.tricks作为对象的属性:

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,可能会很清楚:

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

这将输出您实际期望的内容。

答案 1 :(得分:2)

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

如需更全面的解释,请查看:Python Class Attributes: An Overly Thorough Guide

答案 2 :(得分:2)

您的第一个案例创建一个类变量,第二个案例创建一个实例变量。

当您引用self.foo时,Python首先检查实例的命名空间字典中的foo元素,然后检查类中的foo元素&#39 ; s命名空间字典。

在第一种情况下,由于您创建了一个名为tricks且具有可变类型(列表)的类变量,并且没有专门为该方法重新分配它,因此可以对该列表进行修改到班级的每个实例。

在你的第二种情况下,事情是相同的,除了你用类名的实例变量隐藏类变量,所以从那时起,对self.tricks的所有引用都引用实例变量而不是类变量

第二个案例说明:

mytest.__dict__ = {
    'name': 'test1',
    'tricks': [],  # this doesn't get updated
}

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

答案 3 :(得分:2)

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

class mytest:
    name = "test1"
    tricks = list()
    def __init__(self,name):
        ...
        self.tricks.append(name)

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

class mytest:
    name = "test1"
    tricks = list()
    def __init__(self,name):
        ...
        self.tricks = [name]
        ...

self.tricks = [name]语句将名为tricks的属性提供给self,即mytest实例。 该课程仍然有一个共同的tricks成员。

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

因此,您的第一个示例的实例没有tricks属性,但Python会为您提供所有共享的mytest.tricks。 另一方面,第二个示例的实例确实有自己的tricks属性,Python将返回此属性。

答案 4 :(得分:1)

这个问题需要注意一点。

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

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

这类似于父母和子女;当子类没有为现有函数赋予不同的定义时,调用它会调用父函数。但如果你这样做,就会调用孩子的功能。

答案 5 :(得分:0)

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

在第一种情况下,您要改变self.tricks列表,改变列表不要替换它。所以在整个执行期间,你有一个单独的列表,正在变异。

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

您可以轻松地反省这一点:

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")

,并提供:

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