【问题标题】:Python Class Members InitializationPython 类成员初始化
【发布时间】:2023-04-08 04:58:01
【问题描述】:

我最近刚刚与 Python 中的一个错误作斗争。这是那些愚蠢的新手错误之一,但它让我想到了 Python 的机制(我是一名长期的 C++ 程序员,对 Python 不熟悉)。我将列出错误代码并解释我做了什么来修复它,然后我有几个问题......

场景:我有一个名为 A 的类,它有一个字典数据成员,下面是它的代码(当然这是简化):

class A:
    dict1={}

    def add_stuff_to_1(self, k, v):
        self.dict1[k]=v

    def print_stuff(self):
        print(self.dict1)

使用此代码的类是B类:

class B:

    def do_something_with_a1(self):
        a_instance = A()
        a_instance.print_stuff()        
        a_instance.add_stuff_to_1('a', 1)
        a_instance.add_stuff_to_1('b', 2)    
        a_instance.print_stuff()

    def do_something_with_a2(self):
        a_instance = A()    
        a_instance.print_stuff()            
        a_instance.add_stuff_to_1('c', 1)
        a_instance.add_stuff_to_1('d', 2)    
        a_instance.print_stuff()

    def do_something_with_a3(self):
        a_instance = A()    
        a_instance.print_stuff()            
        a_instance.add_stuff_to_1('e', 1)
        a_instance.add_stuff_to_1('f', 2)    
        a_instance.print_stuff()

    def __init__(self):
        self.do_something_with_a1()
        print("---")
        self.do_something_with_a2()
        print("---")
        self.do_something_with_a3()

请注意,每次调用 do_something_with_aX() 都会初始化一个新的 A 类“干净”实例,并在添加前后打印字典。

错误(如果您还没有弄清楚):

>>> b_instance = B()
{}
{'a': 1, 'b': 2}
---
{'a': 1, 'b': 2}
{'a': 1, 'c': 1, 'b': 2, 'd': 2}
---
{'a': 1, 'c': 1, 'b': 2, 'd': 2}
{'a': 1, 'c': 1, 'b': 2, 'e': 1, 'd': 2, 'f': 2}

在类A的第二次初始化中,字典不为空,而是以最后一次初始化的内容开始,以此类推。我希望他们能够“重新开始”。

解决这个“bug”的显然是添加:

self.dict1 = {}

在 A 类的 __init__ 构造函数中。然而,这让我想知道:

  1. 在 dict1 的声明点(A 类的第一行)初始化“dict1 = {}”是什么意思?没意义?
  2. 导致复制上次初始化的引用的实例化机制是什么?
  3. 如果我在构造函数(或任何其他数据成员)中添加“self.dict1 = {}”,它如何不影响先前初始化实例的字典成员?

编辑:按照答案,我现在明白,通过声明一个数据成员而不是在 __init__ 或其他地方将其引用为 self.dict1,我实际上是在定义 C++/Java 中所谓的静态数据成员.通过将其称为 self.dict1 我将其设为“实例绑定”。

【问题讨论】:

  • 你应该使用新式类,派生自对象。
  • A 类没有__init__()。您是否故意没有,这相当于一个空的def __init__(self): pass,因此它当然没有数据成员?如果没有,请修复您的代码。

标签: python class initialization


【解决方案1】:

您一直提到的错误是documented,这是 Python 类的标准行为。

像您最初所做的那样在__init__ 之外声明一个字典就是声明一个类级变量。它一开始只创建一次,每当您创建新对象时,它都会重用相同的字典。要创建实例变量,请在 __init__ 中使用 self 声明它们;就这么简单。

【讨论】:

  • 我没有说它是 Python 错误,我说这是我的错误...当程序员根据文档做某事时,它仍然是一个错误(正如我提到的一个愚蠢的错误)。我的问题是关于触发这种“记录在案的标准行为”的机制 - 我想了解引擎盖下的内容。谢谢。特别是我的第一个问题,我想了解声明初始化是否无用。
  • 不是没用的;如果您希望变量通过实例持续存在,您将使用它。您的问题是,当您执行 x = A() 时,只会执行 init 代码。类变量保持不变。
  • 好吧,只要声明不在init中,就和C++中的“静态”数据成员一样,在“init" 将其从静态更改为实例绑定?现在很清楚了,谢谢。
  • 差不多。文档中谈到了它与 C++ 的一些区别,您应该检查一下。
  • @Rax 是技术性的,它 dict1 不必在 init 中声明;它可以在任何方法中声明...只是 init 将始终被执行(除非对 new 进行更改)。最好记住的是,python 中从来没有任何隐含的“自我”。如果你没有明确地分配给 self.member 或从 self.member 中读取,你就没有接触到这个实例。
【解决方案2】:

当您访问实例的属性时,例如 self.foo,python 将首先在 self.__dict__ 中找到“foo”。如果没有找到,python 会在TheClass.__dict__找到'foo'

在您的情况下,dict1 属于 A 类,而不是实例。

【讨论】:

  • 这实际上有助于我直观地了解其工作原理。因此,一旦将 self.x 绑定到对象中的某些内容(在 init 或其他地方),未来的 self.x 查找将在您的实例范围内引用该新变量。如果他们在实例范围内没有找到它,他们会在类范围内查找。
【解决方案3】:

@Matthew:请查看面向对象编程中类成员和对象成员之间的区别。发生此问题是因为原始 dict 的声明使其成为类成员,而不是对象成员(正如原始发布者的意图)。因此,对于类的所有实例(即一次),它存在一次(共享)对于类本身,作为类对象本身的成员),因此行为是完全正确的。

【讨论】:

    【解决方案4】:

    Python 的类声明作为代码块执行,任何局部变量定义(其中函数定义是一种特殊类型)都存储在构造的类实例中。由于 Python 中属性查找的工作方式,如果在实例上找不到属性,则使用类上的值。

    Python 博客历史上的an interesting article about the class syntax

    【讨论】:

      【解决方案5】:

      如果这是您的代码:

      class ClassA:
          dict1 = {}
      a = ClassA()
      

      那么你可能预计这会发生在 Python 中:

      class ClassA:
          __defaults__['dict1'] = {}
      
      a = instance(ClassA)
      # a bit of pseudo-code here:
      for name, value in ClassA.__defaults__:
          a.<name> = value
      

      据我所知, 会发生什么,除了 dict 复制了它的指针,而不是值,这是 Python 中所有地方的默认行为。看这段代码:

      a = {}
      b = a
      a['foo'] = 'bar'
      print b
      

      【讨论】:

        猜你喜欢
        • 2013-02-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-10
        • 2016-08-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多