【问题标题】:is it a good practice to assign a class instance to a class attribute in python?将类实例分配给python中的类属性是一种好习惯吗?
【发布时间】:2015-03-11 03:43:03
【问题描述】:

我正在尝试在 python 中使用类组合,我想知道将类实例分配给 python 中的类属性是否是一种好习惯。以下是我想到的两个例子。如果一些有经验的程序员能告诉我何时使用哪个,我将不胜感激。

(1)

class A:
   def __init__(self):
      self.x = 10

class B:
   def __init__(self):
      self.y = A()

objB = B()

(2)

class A:
   def __init__(self):
      self.x = 10

class B:
   def __init__(self):
      self.y = None

objB = B()
objB.y = A()

【问题讨论】:

    标签: python class object relationship composition


    【解决方案1】:

    这样的事情总是描述性的!每个B 都有一个A 对象吗?如果是这样,那么你最好使用#1。 B 是某种容器,在这种情况下有一个 A 对象吗?使用#2。

    例如,假设我正在构建Tile 对象的地图。每个Tile 对象都有一个location,我将其进一步抽象为Location 对象,如下所示:

    class Tile(object):
        def __init__(self, location, *args, **kwargs):
            self.location = location
    
    class Location(object):
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
    x2_y3 = Location(2, 3)
    tile = Tile(x2_y3)
    tile.location.x # 2
    

    现在我也可以这样做:

    class Tile(object):
        def __init__(self, location: "(x, y)", *args, **kwargs):
            x, y = location
            self.location = Location(x, y)
    
    class Location(object):
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
    tile = Tile(2,3) # builds its own location object
    

    在这种情况下,第二种策略可能更可取,但我更喜欢第一种,因为它更易于扩展。如果以后我转移到 3D 空间,我可以这样做:

    class Location3D(Location):
        def __init__(self, x, y, z):
            self.z = z
            super().__init__(x, y)
    
    tile = Tile(Location3D(2, 3, -1))
    

    在第二种策略中,这将失败,因为Tiles 总是有Location 对象。您必须对 Tile 进行猴子补丁才能使其正常工作,或者构建一个新的 Tile3D 对象,该对象可能与其他 Tiles 配合得很好,也可能不适合。

    【讨论】:

    • 所以据我了解,选择正确的策略取决于我们的目的?
    • @tsubasa 就像其他一切一样! :)
    【解决方案2】:

    重新。 1),我会显式传入 A 实例,而不是在 B.__init__ 中构建它。原因是,在不平凡的情况下,您将希望 A 具有不同的初始化值。否则您可能需要共享 A 实例 - 否则,这是一个 1-1 关系,这是有限制的。

    class A:
       def __init__(self):
          self.x = 10
    
    class B:
       def __init__(self, instanceA):
          self.y = instanceA
    
    #fire-and forget A
    objB = B(A())
    
    #can still do something with the instance
    a = A()
    objB2 = B(a)
    

    例如,我经常使用具有“经理”或“父”属性的 B 类。比如说,一个具有 n 个 Column 子级的 Table 类。每列都有一个对父表的引用,它们不会在运行时组成一个。

    如果您基本上不需要担心实例的初始值,则可以进一步细化:

    class B:
       #instanceA uses a default value of None
       def __init__(self, instanceA = None):
          #assign the instanceA if it was passed or build your own on the fly
          if instanceA is None:
              self.y = A()
          else:
              self.y = instanceA
    

    附言不要这样做来启动默认的 A 实例。默认值只计算一次,只要您没有显式传入值,就会分配相同的 A 实例。

       def __init__(self, instanceA = A()):
            ....
    

    重新。 2)我不会使用事后构图。如果您忘记设置 .y 属性怎么办?实例应该尽可能地不需要多个链式调用和修改才能使用(几乎是 skrrgwasme 所说的)。 Python 具有这种灵活性,是的,这并不意味着它应该在没有充分理由的情况下使用。

    最后,虽然您不需要设置 self.y = None,但某些 Python 代码分析工具仅在 __init__ 方法上设置实例属性时才会提取它们。

    【讨论】:

    • 请注意,在您的最后一个示例中,常见的成语是if instanceA is None: self.y = A(); else: self.y = instanceA。这可以保护您免受可能传递虚假值的情况的影响,例如如果A 描述了一些类似列表的对象,而您实际要挂接到新对象的列表当前为空。
    • 正确。你的建议是我自己通常使用的。猜我对这里的代码太“聪明”了,要修复。
    【解决方案3】:

    我会推荐方法 1,因为它比方法 2 更支持封装。

    为什么我们关心封装?方法一中,objB可以在一条语句中初始化,所以追寻初始化bug的地方比较少。想要使用你的类的代码不需要知道初始化它所需的一系列步骤;只有一个语句和一个参数列表。这种简单性会导致更少的错误以及更灵活和可维护的代码。

    尽管在 Python 中封装没有像在其他一些语言(如 Java)中那样严格执行,但它仍然是一种很好的做法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多