"Hidden" 使用继承时 python 中的属性或重复代码

"Hidden" attributes or duplicated code in python when using inheritance

我有一个问题,关于在 python

中使用继承时,我认为什么是潜在的坏习惯

假设我有一个基地class

class FourLeggedAnimal():
    def __init__(self, name):
        self.name = name
        self.number_of_legs = 4

和两个女儿classes

class Cat(FourLeggedAnimal):
    def __init__(self, name):
        super().__init__(name)

    def claw_the_furniture(self):
        for leg in range(self.number_of_legs):
        print("scratch")
class Dog(FourLeggedAnimal):
    def __init__(self, name):
        super().__init__(name)
     
    def run_in_sleep(self):
        for leg in range(self.number_of_legs):
        self.move_leg(leg)

    def move_leg(i):
        pass

出于本示例的目的,我打算将 Animal 保存在 Cat 不同的文件 中。对于阅读 CatDog class 代码的人来说,使用了 number_of_legs 属性但未在文件中定义。我的理解是最好不要有定义不透明的变量(这就是为什么最好避免 from x import *.

我看到了在两个子 classes 中重复定义 self.number_of_legs 的替代方案,但这违背了继承的目的。

是否有处理这种情况的最佳实践?

Is there a best-practice to deal with this kind of situation?

通常,class 变量用于此目的。

class FourLeggedAnimal():
    number_of_legs = 4                    # class variable

    def __init__(self, name):
        self.name = name

class Cat(FourLeggedAnimal):
    def __init__(self, name):
        super().__init__(name)

    def claw_the_furniture(self):
        for leg in range(self.number_of_legs):
            print("scratch")

class Dog(FourLeggedAnimal):
    def __init__(self, name):
        super().__init__(name)
     
    def run_in_sleep(self):
        for leg in range(self.number_of_legs):
            self.move_leg(leg)

    def move_leg(i):
        pass

请注意,即使这些 classes 在不同的文件中,该属性也是父 public API 的一部分,子 classes 可以知道.此外,class 名称“FourLeggedAnimal”很好地传达了腿的数量。

My understanding is that it is best not to have variables whose definitions are opaque (which is why its best to avoid from x import *.

我想您可能误解了这条建议的来源。它甚至可能是不同建议的混合体。我将尝试解释我认为可能是人们试图传达的潜在想法。

首先,人们普遍认为 Python 最好避免使用 from x import *。这是因为它使读者很难找出名称的来源,或者是否确实定义了名称。它还混淆了一些代码分析工具。这是(非内置)名称通常进入顶级命名空间而不会出现在源代码中并且易于搜索的唯一方法。就此建议而言,在这种情况下 。如果您不能在对象上使用字段和方法,那么您根本无法编写 Python 代码,而且您通常有一个清晰的面包屑痕迹可供遵循。 (此外,如果您使用类型注释。)

不过,你也可能想到了封装的原理。在面向对象的编程中,最好将对象的 接口 实现 分开。您可以使界面尽可能小、简单和清晰,并使用 对象从代码中隐藏实现。通过这种方式,您可以独立地推理和更改实现,并确信这样做不会影响其他代码。这个原则甚至适用于 base classes 和 sub-classes - sub-class 不应该“知道”关于它不知道的 base class 的任何事情不需要。现在,修改变量,以及在较小程度上阅读可修改的变量,需要非常了解基数 class 对它们的值的期望、它们与其他状态的关系以及它们何时 possible/permissible 可以改变.依赖它们会使安全更改基数变得更加困难 class.

现在,Python在这方面确实比其他一些语言具有更大的灵活性。在 Python 中,您可以用 属性 无缝替换变量,从而将“读取”和“设置”字段转换为您可以根据需要实现的方法。在其他语言中,一旦子 class 开始使用由基 class 公开的字段,就不可能重构基 class 以删除该字段或在访问时添加任何额外行为,除非您还更新所有子 classes。所以这 不那么令人担忧。或者更确切地说,没有特别的理由将字段与方法区别对待。

考虑到所有这些,问题就变成了 - 您的基础 class 向其子 class 呈现的界面是什么?它是否支持他们设置以及读取该字段?您能否减少两个 class 之间接口的大小和复杂性而不会使您的代码更复杂?如果一个接口是只读的,那么它更简单,更容易推理,如果它根本不涉及可变状态则更是如此。在可能的情况下,基 class 不应给子 class 任何不必要的机会来破坏其不变量。 (即它对其自身状态的期望。)在 Python 中,这些事情通常通过约定实现(例如,以下划线开头的字段和方法被认为不是 public 接口的一部分,除非另有说明) 和文档而不是通过语言功能。