【问题标题】:Are the attributes in a Python class shared or not? [duplicate]Python 类中的属性是否共享? [复制]
【发布时间】:2017-12-30 06:58:02
【问题描述】:

下面的代码让我很困扰:-

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']

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

【问题讨论】:

  • 第二种情况与类属性无关。它是底层对象。每次调用 __init__ 都会创建一个新的列表对象:[name]。因此,每个实例的属性tricks所引用的列表是独立的。
  • @MosesKoledoye 而新创建的列表[name] 覆盖了旧列表tricks ?那么旧对象tricks 被删除了吗?
  • 如果你写self.tricks = [name],那么self会收到一个属性tricks,它会影响类成员myclass.tricks
  • 第二个代码创建了一个实例属性self.tricks,它隐藏了类属性mytest.tricks。所以如果你想在第二个代码中访问mytest.tricks,你需要使用mytest.tricks这个名字。
  • 您可能希望遵守class naming conventions,即使是玩具示例,也只是为了练习。

标签: python list class


【解决方案1】:

不同之处在于,在您的第二个示例中,您将创建一个新列表 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(因为它尚未创建),那么它会尝试将其作为类的成员来查找。既然有tricks,那你就可以访问了。

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

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

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

【讨论】:

    【解决方案2】:

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

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

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

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

    说明的第二种情况:

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

    【讨论】:

      【解决方案3】:

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

      更详尽的解释,请看:Python Class Attributes: An Overly Thorough Guide

      【讨论】:

        【解决方案4】:

        在您的第一个示例中,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 首先在instance.__dict__ 中查找tricks 成员。如果没有找到,它会在type(instance).__dict__ 中查找tricks 成员。

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

        【讨论】:

          【解决方案5】:

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

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

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

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

          【讨论】:

            【解决方案6】:

            在这两种情况下,您将 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
            

            【讨论】:

              猜你喜欢
              • 2011-02-22
              • 1970-01-01
              • 1970-01-01
              • 2017-10-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-06-16
              • 1970-01-01
              相关资源
              最近更新 更多