【问题标题】:python: class attributes and instance attributespython:类属性和实例属性
【发布时间】:2011-11-24 19:13:12
【问题描述】:

我是 python 新手,了解到类属性就像 C++ 中的静态数据成员。但是,我在尝试以下代码后感到困惑:

>>> class Foo:
...     a=1
... 
>>> f1=Foo();
>>> f2=Foo()
>>> f1.a
1
>>> f1.a=5
>>> f1.a
5
>>> f2.a
1

f2.a 不应该也等于 5 吗?

如果 a 被定义为列表而不是整数,则行为是预期的:

>>> class Foo:
...     a=[]
... 
>>> f1=Foo();
>>> f2=Foo()
>>> f1.a
[]
>>> f1.a.append(5)
>>> f1.a
[5]
>>> f2.a
[5]

我看了 Python: Difference between class and instance attributes,但它没有回答我的问题。

谁能解释为什么会有差异?谢谢

【问题讨论】:

标签: python


【解决方案1】:

Python 的类属性和对象属性存储在单独的dictionaries 中。对于对象f1,可以分别通过f1.__class__.__dict__f1.__dict__ 访问。执行print f1.__class__ is Foo会输出True

当你引用一个对象的属性时,Python 首先尝试在对象字典中查找它。如果在那里没有找到它,它会检查类字典(依此类推)。

当您分配给f1.a 时,您将在f1 的对象字典中添加一个条目。 f1.a 的后续查找将找到该条目。查找f2.a 仍然会找到类属性——类属性字典中的条目。

您可以通过删除 f1.a 将其恢复为 1

del f1.a

这将删除f1 对象字典中a 的条目,随后的查找将继续到类字典。所以,之后,print f1.a 会输出 1。

【讨论】:

  • 我觉得这个答案比较详细
  • @Yuncy 我也这么认为
【解决方案2】:

您在第二个示例中没有做同样的事情。在您的第一个示例中,您正在为 f1.a 分配一个新值:

f1.a = 5

在您的第二个示例中,您只是扩展了一个列表:

f1.a.append(5)

这不会改变f1.a 指向的内容。如果您改为这样做:

f1.a = [5]

您会发现这与您的第一个示例的行为相同。

但是考虑这个例子:

>>> f1=Foo()
>>> f2=Foo()
>>> Foo.a = 5
>>> f1.a
5
>>> f2.a
5

在这个例子中,我们实际上是在改变 class 属性的值, 并且更改在类的所有实例中都是可见的。当你 类型:

f1.a = 5

您正在用 instance 属性覆盖 class 属性。

【讨论】:

  • “您正在用实例属性覆盖类属性。”我不这么认为。 class 属性保持不变。在用户 11869 的代码中,属性 a 是由指令 f1.a = 5f1 的命名空间中创建的,因为该属性以前不存在,因为它是在我的回答中使用 __dict__
【解决方案3】:
>>> class Foo:
...     a=1
... 
>>> f1=Foo()
>>> f2=Foo()
>>> f1.a    # no instance attribute here in f1, so look up class attribute in Foo
1
>>> f1.a=5  # create new instance attribute in f1
>>> f1.a    # instance attribute here. Great, let's use this.
5
>>> f2.a    # no instance attribute in f2, look up class attribute in Foo
1
>>>
>>> class Foo:
...     a=[]
... 
>>> f1=Foo()
>>> f2=Foo()
>>> f1.a            # no instance attribute - look up class attribute in Foo
[]
>>> f1.a.append(5)  # no instance attribute, modify class attribute in-place
>>> f1.a            # no instance attribute - look up class attribute in Foo
[5]
>>> f2.a            # same here
[5]

【讨论】:

    【解决方案4】:

    我的建议:要了解此类情况,请使用id__dict__ 进行测试:

    class Foo:
        a=1
    
    # Foo.a == 1
    # id(Foo.a) == 10021840
    # Foo.__dict__ == {'a': 1, '__module__': '__main__', '__doc__': None}
    
    
    f1 = Foo()
    
    # f1.a == 1
    # id(f1.a) == 10021840
    # f1.__dict__ == {}
    
    f2 = Foo()
    
    # f2.a == 1
    # id(f2.a) == 10021840
    # f2.__dict__ == {}
    
    f1.a = 5
    
    # f1.a == 5
    # id(f1.a) == 10021792
    # f1.__dict__ == {'a': 5}
    
    # f2.a == 1
    # id(f2.a) == 10021840
    # f2.__dict__ == {}
    

    这表明只要指令f1.a = 5没有被执行,实例f1就没有个人属性a

    那么,为什么在f1.a = 5之前执行的指令print f1.a会产生1
    那是因为:

    一个类实例有一个实现为字典的命名空间,它是 搜索属性引用的第一个位置。当一个 在那里找不到属性,并且实例的类有一个 通过该名称的属性,搜索继续与类 属性。

    http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy

    【讨论】:

      【解决方案5】:

      会发生以下情况:

      当您实例化一个新对象f1 = Foo() 时,它没有任何自己的属性。每当您尝试访问例如f1.a 时,您都会被重定向到班级的Foo.a

      print f1.__dict__
      {}
      
      print f1.a
      1
      

      但是,如果您设置 f1.a = 5,实例将获得该值的新属性:

      print f1.__dict__
      {'a': 5}
      

      类定义不受此影响,任何其他实例也是如此。

      在第二个示例中,您没有重新分配任何内容。使用append,您只使用在类中定义的相同列表。因此,您的实例仍然引用该列表,所有其他实例也是如此。

      【讨论】:

      • 与我要输入的答案相同。我只想补充一点,它与列表“工作”的原因是因为在这种情况下,对象是mutable。由于所有实例都引用同一个对象,因此所有实例都可以看到对该对象的修改。
      • @Debilski:新实例实际上通常具有自己的属性(您在答案中写的f1.__dict__ 就是其中之一,比较id(Foo.a)id(f1.a) 可以看出) .
      【解决方案6】:

      当您在 python 中分配一个属性时,该属性可能已经在哪里定义并不重要,新的分配总是应用于分配给的对象。当你说

      >>> f1.a=5
      

      这里有属性的对象就是实例,所以获取新值的是实例。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-04-14
        • 2013-11-18
        • 2018-06-12
        • 2021-12-26
        • 2010-11-21
        • 1970-01-01
        相关资源
        最近更新 更多