【问题标题】:Why the class attribute does not always appear as key in the instance's dictionary?为什么类属性并不总是作为键出现在实例的字典中?
【发布时间】:2021-02-10 00:02:21
【问题描述】:

我正在阅读有关 Attributes 的这部分关于 Python 中的 OOP 的内容,并且对下面的示例感到震惊。

我不明白为什么实例的字典(其 class 被赋予了新属性)为空:

class Robot(object):
    pass

x = Robot()
Robot.brand = "Kuka"
x.brand

输出:

'Kuka'

x.brand = "Thales"
Robot.brand

输出:

'Kuka'

y = Robot()
y.brand

输出:

'Kuka'

Robot.brand = "Thales"
y.brand

输出:

'Thales'

x.brand

输出:

'Thales'

如果您查看__dict__ 字典,您可以看到发生了什么:

x.__dict__

输出:

{'brand': 'Thales'}

y.__dict__

输出:

{}

来自同一网站的另一句话:

如果您尝试访问 y.brand,Python 会首先检查“brand”是否是 y.__dict__ 字典的键。如果不是,Python 会检查“brand”是否是 Robot.__dict__ 的键。如果是这样,则可以检索该值。

我的问题是:为什么y.__dict__ 会给出一个空字典?其背后的逻辑和机制是什么?

【问题讨论】:

    标签: python class object oop attributes


    【解决方案1】:

    为什么会有一个?实例不会自动复制类属性(这将是意外且低效的)。您的 y 对象没有任何实例属性(y.brand 只是 Robot.brand 的代理,因为您在问题中发布的文档中引用了此内容),因此其 .__dict__ 为空。

    【讨论】:

      【解决方案2】:

      查看this tutorial on __dict__,我们可以看到如下代码示例(对原始代码稍作修改):

      class MyClass(object):
          class_var = 1
      
          def __init__(self, i_var):
              self.i_var = i_var
      
      foo = MyClass(2)
      bar = MyClass(3)
      
      print(foo.__dict__)
      print(bar.__dict__)
      

      输出如下:

      {'i_var': 2}
      {'i_var': 3}
      

      如您所见,foo.__dict__bar.__dict__ 的输出包括class_var。所有类都是这种情况; __dict__ 没有列出类范围的属性。

      查看您的整个程序:

      class Robot(object):
          pass
      x = Robot()
      Robot.brand = "Kuka"
      x.brand
      x.brand = "Thales"
      Robot.brand
      y = Robot()
      y.brand
      Robot.brand = "Thales" # Sets class variable
      y.brand
      x.brand
      x.__dict__
      y.__dict__
      

      我们可以看到,在创建 xy 时,Robot.brand 被设置为 "Kuka"。我们还看到有一个显式声明设置x.brand"Thales";但是,它不包含 y.brand = "Whatever" 声明。

      也就是说y.brand的值是继承自类属性Robot.brand,类属性没有与__dict__一起列出。另一方面,x.brand 专门设置为"Thales",因此x.brand 现在指的是特定于对象的变量,这意味着它将显示在__dict__ 列表中。

      【讨论】:

        【解决方案3】:

        当你尝试访问实例中的方法或属性时,python 首先询问实例是否有这样的东西。如果没有,那么 python 会询问父级是否有那个东西,等等。Python 继续爬上继承层,直到它找到一些东西或者它引发和错误。例如,

        class A: 
            x = 9
        

        “x”是一个类变量,与类一起存在。因此,所有实例都自动具有“x”属性,除非它们覆盖自己的属性。

        I = A()
        print(I.x) #prints 9, python asks the parent for "x" since I didn't have an "x"
        I.x = 10    
        print(I.x) #prints 10, python returns what I already has, doesn't ask parent
        
        Z = A() 
        print(Z.x) #prints 9, python asks the parent for "x" since Z didn't have an "x"
        

        这非常节省内存。例如,这意味着所有实例共享一个“全局”(相对于类的全局)变量。因此,只有一份 A.x 被写入内存。 python在内存中写入另一个“x”值的唯一时间是实例显式更改了它们的“x”值。因此,生成一百万个 A 实例不会生成一百万个 A.x. 值的副本

        【讨论】:

          【解决方案4】:

          我们可以使用id(<object>) 来检查发生了什么。

          声明一个新的空类Robot

              class Robot(object):
              ...     pass
              ... 
              Robot
              <class '__main__.Robot'>
              Robot.__dict__
              mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Robot' objects>, '__weakref__': <attribute '__weakref__' of 'Robot' objects>, '__doc__': None})
          
          

          Robot 类的新实例

              x = Robot()
              x
              <__main__.Robot object at 0x0000022811C8A978>
          

          x 实例为空

              x.__dict__
              {}
          

          定义了新的类属性brand

          Robot.brand = "Kuka"
          Robot.brand
          'Kuka'
          

          如果我们尝试访问x.brand,Python 会在x.__dict__ 中查找brand,但什么也找不到,所以它会转到Robot.__dict__ 并找到类属性brand

          x.brand
          'Kuka'
          

          我们可以验证我们实际上看到的是相同的

          id(Robot.brand)
          2371120205752
          id(x.brand)
          2371120205752
          

          定义了新的实例属性brand

          x.brand = "Thales"
          

          并且类属性brand 保持不变

          Robot.brand
          'Kuka'
          

          我们可以验证我们实际上看到了两个不同的属性

          id(x.brand)
          2371119992200
          id(Robot.brand)
          2371120205752
          

          新实例 y 已创建并且为空

          y = Robot()
          y.__dict__
          {}
          

          我们可以验证这是一个新的:

          id(y)
          2371119989200
          

          如果我们尝试访问y.brand,Python 将在y.__dict__ 中寻找brand,但什么也没找到,然后转到Robot.__dict__ 并找到类属性brand

          y.brand
          'Kuka'
          

          我们可以验证 id 是否与Robot.brand 相同。所以y是对Robot.brand的引用

          id(y.brand)
          2371120205752
          

          如果我们修改类属性brand

          Robot.brand = "Thales"
          id(Robot.brand)
          2371119992200
          

          y.brand 被修改,因为此时不是实例属性,而是对类属性Robot.brand 的引用。我们可以检查y.brand的id是否与Robot.brand相同。

          y.brand
          'Thales'
          id(y.brand)
          2371119992200
          

          现在我们可以检查x 有一个实例属性x.brand

          x.brand
          'Thales'
          x.__dict__
          {'brand': 'Thales'}
          

          但是 y 什么都没有,因为它只是对 Robot.brand 的引用

          y.__dict__
          {}
          
          

          Robot.brand 具有类属性 brand,其值为 Thales

          Robot.__dict__
          mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Robot' objects>, '__weakref__': <attribute '__weakref__' of 'Robot' objects>, '__doc__': None, 'brand': 'Thales'})
          

          在此处查看其他说明:https://github.com/leocjj/0123/blob/master/Python/0123P_9_Classes_objects.txt

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-12-01
            • 1970-01-01
            • 2016-10-26
            • 1970-01-01
            相关资源
            最近更新 更多