将 class 实例分配给 python 中的 class 属性是一种好习惯吗?

is it a good practice to assign a class instance to a class attribute in python?

我正在尝试在 python 中使用 class 组合,我想知道将 class 实例分配给 class 属性是否是一个好习惯在 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()

这样的事情总是描述性的!每个 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 space 我可以改为:

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 对象,该对象可能与其他 Tile 兼容也可能不兼容。

我建议使用方法 1,因为它比方法 2 更支持封装。

为什么我们关心封装?在方法 1 中,objB 可以在单个语句中初始化,因此可以查找初始化错误的地方较少。想要使用您的 class 的代码不需要知道初始化它所需的一系列步骤;只是一个语句和一个参数列表。这种简单性导致更少的错误和更灵活和可维护的代码。

虽然封装在 Python 中不像在某些其他语言(例如 Java 中那样严格执行,但它仍然是一种很好的做法。

回复。 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)

例如,我经常使用具有 'manager' 或 'parent' 属性的 B classes。比如说,一个 Table class 有 n 个子列。每列都获得对父级 table 的引用,它们不会即时生成。

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

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

p.s。不要这样做来启动默认的 A 实例。默认值仅计算一次,只要您未显式传入值,就会分配相同的 A 实例。

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

回复。 2)我不会使用事后组合。如果忘记设置 .y 属性怎么办?实例应该尽可能不需要多次链式调用和修改才能使用(几乎是 skrrgwasme 所说的)。 Python 具有这种灵活性,是的,并不意味着没有充分理由就应该使用它。

最后,虽然您不需要设置 self.y = None,但一些 Python 代码分析工具仅在实例属性设置在 __init__方法。