使用继承时python中的“隐藏”属性或重复代码

使用继承时python中的“隐藏”属性或重复代码,python,oop,inheritance,module,object-oriented-analysis,Python,Oop,Inheritance,Module,Object Oriented Analysis,在python中使用继承时,我有一个问题,我认为这是一个潜在的坏习惯 假设我有一个基类 class FourLeggedAnimal(): def __init__(self, name): self.name = name self.number_of_legs = 4 还有两个女儿班 class Cat(FourLeggedAnimal): def __init__(self, name): super().__init__(

在python中使用继承时,我有一个问题,我认为这是一个潜在的坏习惯

假设我有一个基类

class FourLeggedAnimal():
    def __init__(self, name):
        self.name = name
        self.number_of_legs = 4
还有两个女儿班

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")
在本例中,我打算将动物保存在与猫不同的文件中。对于正在阅读猫或狗类代码的用户,将使用“腿数”属性,但未在文件中定义。我的理解是最好不要有定义不透明的变量,这就是为什么最好避免x导入*

我认为另一种方法是在两个子类中重复self.number_的定义,但这违背了继承的目的

有没有处理这种情况的最佳做法

有没有处理这种情况的最佳做法

通常,类变量用于此目的

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
请注意,即使这些类位于不同的文件中,该属性也是父类的公共API的一部分,并且子类可以知道该属性。另外,类名FourLeggedAnimal在传达腿的数量方面做得很好

有没有处理这种情况的最佳做法

通常,类变量用于此目的

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
请注意,即使这些类位于不同的文件中,该属性也是父类的公共API的一部分,并且子类可以知道该属性。另外,类名FourLeggedAnimal在传达腿的数量方面做得很好

我的理解是最好不要有定义不透明的变量,这就是为什么最好避免x导入*

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

首先,人们普遍认为在Python中最好避免使用from x import*。这是因为它使读者很难找到一个名字的来源,或者确实是它的定义。它还混淆了一些代码分析工具。这是非内置名称通常进入顶级名称空间的唯一方法,而不会出现在源代码中,也不会很容易搜索。就这条建议而言,它只适用于这种情况。如果不能在对象上使用字段和方法,您几乎无法编写Python代码,而且您通常有一条清晰的线索可以遵循。如果您使用的是类型注释,则更是如此

然而,您可能也在考虑封装的原理。在面向对象编程中,最好将接口与对象的实现分开。您可以使接口尽可能小、简单和清晰,并使用对象将实现隐藏在代码之外。通过这种方式,您可以独立地对实现进行推理和更改,确信这样做不会影响其他代码。这个原则甚至适用于基类和子类之间——子类不应该知道它不需要的任何关于基类的信息。现在,修改变量,以及在较小程度上读取可修改变量,需要了解基类对其值的期望值、它们与其他状态的关系以及它们可能/允许更改的时间。依赖它们会使安全地更改基类变得更加困难

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

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

我的理解 最好不要有定义不透明的变量,这就是为什么最好避免x导入*

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

首先,人们普遍认为在Python中最好避免使用from x import*。这是因为它使读者很难找到一个名字的来源,或者确实是它的定义。它还混淆了一些代码分析工具。这是非内置名称通常进入顶级名称空间的唯一方法,而不会出现在源代码中,也不会很容易搜索。就这条建议而言,它只适用于这种情况。如果不能在对象上使用字段和方法,您几乎无法编写Python代码,而且您通常有一条清晰的线索可以遵循。如果您使用的是类型注释,则更是如此

然而,您可能也在考虑封装的原理。在面向对象编程中,最好将接口与对象的实现分开。您可以使接口尽可能小、简单和清晰,并使用对象将实现隐藏在代码之外。通过这种方式,您可以独立地对实现进行推理和更改,确信这样做不会影响其他代码。这个原则甚至适用于基类和子类之间——子类不应该知道它不需要的任何关于基类的信息。现在,修改变量,以及在较小程度上读取可修改变量,需要了解基类对其值的期望值、它们与其他状态的关系以及它们可能/允许更改的时间。依赖它们会使安全地更改基类变得更加困难

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


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

我建议您发表评论。我不确定是否所有人都会同意我的意见,但从工作经验中我了解到,我的许多同事实际上更喜欢将变量从某些文件中抽象出来,这意味着它们是在其他地方定义的。在我看来,我不认为这种风格有什么不对,实际上我更喜欢它。大多数IDE还将有一个“转到定义”选项,其他人可以在其中查看该变量的定义位置。再说一次,这只是我个人的观点。这个定义并不是很模糊。_支腿的数量由self限定,因此如果未在子类中定义,则必须在超类中定义。重复定义充其量也无济于事,可能会导致错误。我建议发表评论。我不确定是否所有人都同意我的观点,但从工作经验中我了解到,我的许多同事实际上更喜欢将变量从某些文件中抽象出来,这意味着它们是在其他地方定义的。在我看来,我不认为这种风格有什么不对,实际上我更喜欢它。大多数IDE还将有一个“转到定义”选项,其他人可以在其中查看该变量的定义位置。再说一次,这只是我个人的观点。这个定义并不是很模糊。_支腿的数量由self限定,因此如果未在子类中定义,则必须在超类中定义。重复定义充其量也无济于事,并可能导致错误。