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

2024-10-03 02:44:24 发布

您现在位置:Python中文网/ 问答频道 /正文

在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")
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类代码的人,使用了number_of_legs属性,但未在文件中定义。我的理解是最好不要有定义不透明的变量(这就是为什么最好避免from x import *

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

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


Tags: ofnameinselfnumberfor定义init
2条回答

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

通常,类变量用于此目的

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”在传达腿的数量方面做得很好

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代码,而且您通常有一条清晰的线索可以遵循。(如果您使用的是类型注释,则更应如此。)

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

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

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

相关问题 更多 >