【问题标题】:Overriding abstract methods in python覆盖python中的抽象方法
【发布时间】:2018-04-24 18:07:16
【问题描述】:

在覆盖python抽象方法时,有没有办法在方法签名中使用额外的参数来覆盖该方法?

例如

摘要 class=

Agent(ABC):

    @abstractmethod
    def perceive_world(self, observation):
        pass

继承类:

Dumb_agent(Agent):

    def perceive_world(self, observation):
        print('I see %s' % observation)

在方法签名中使用额外参数继承类:

Clever_Agent(Agent):

    def perceive_world(self, observation, prediction):
        print('I see %s' % observation)
        print('I think I am going to see %s happen next' % prediction)

【问题讨论】:

  • 好吧,如果你尝试一下会发生什么?
  • 虽然您可以,但请不要。这将是令人困惑和容易出错的。人们会期望代理有一个 1 参数 perceive_world 方法。
  • 如果您希望它完全有用,您可能希望抽象方法采用额外的参数(可能使用默认值,甚至可能使用不希望该参数断言的类他们得到了默认值)。否则,知道一个对象遇到代理 ABC 并不能真正告诉你如何使用它,那为什么还要有一个 ABC?
  • 或者,或者,只是让 Clever_Agent 为额外参数提供一个默认值,如果默认值被传递,让它做一些合理的事情。
  • @pseudogram 在像 Java 这样的语言中,这不算作接口方法的实现。在 Python 中,它_会_——但只是偶然的。

标签: python abstract-methods


【解决方案1】:

你正在尝试做的事情会奏效——但这是一个非常糟糕的主意。

通常,您不希望在覆盖时以不兼容的方式更改方法的签名。这是Liskov Substitution Principle 的一部分。

在 Python 中,经常有充分的理由违反这一点——继承并不总是与子类型有关。

但是,当您使用 ABC 来定义接口时,这显然是关于子类型化的。这是 ABC 子类和 abstractmethod 装饰器的唯一目的,所以用它们来表示其他任何东西充其量是高度误导。


更详细的:

通过从Agent 继承,您声明Clever_Agent 的任何实例都可以像Agent 一样使用。这包括能够致电my_clever_agent.perceive_world(my_observation)。事实上,它不只是包括。这就是它的全部含义!如果那个调用总是失败,那么没有Clever_Agent 是Agent,所以它不应该声称是。

在某些语言中,您有时需要伪造接口检查的方式,以便稍后可以将类型切换和/或“动态转换”回实际类型。但在 Python 中,这从来都不是必需的。没有“Agents 列表”之类的东西,只是一个无所不包的列表。 (除非您使用可选的静态类型检查——但在这种情况下,如果您需要绕过静态类型检查,请不要声明静态类型只是为了给自己设置障碍。)


在 Python 中,您可以通过添加可选参数将方法扩展到其超类方法之外,这是完全有效的,因为它仍然与显式声明的类型兼容。例如,这将是一件非常合理的事情:

class Clever_Agent(Agent):
    def perceive_world(self, observation, prediction=None):
        print('I see %s' % observation)
        if prediction is None:
            print('I have no predictions about what will happen next')
        else:
            print('I think I am going to see %s happen next' % prediction)

甚至这可能是合理的:

class Agent(ABC):
    @abstractmethod
    def perceive_world(self, observation, prediction):
        pass

class Dumb_agent(Agent):
    def perceive_world(self, observation, prediction=None):
        print('I see %s' % observation)
        if prediction is not None:
            print('I am too dumb to make a prediction, but I tried anyway')

class Clever_Agent(Agent):
    def perceive_world(self, observation, prediction):
        print('I see %s' % observation)
        print('I think I am going to see %s happen next' % prediction)

【讨论】:

  • @abarmert,我也有同样的问题。看完你的回答,我的理解是在子类方法中添加额外的参数是合理的。但是您答案的第一部分似乎不支持这种机制。我对此有点困惑。
【解决方案2】:

在许多方面,从父类重写抽象方法并添加或更改方法签名在技术上不称为 方法重写,您实际上可能正在做的是 方法隐藏强>。方法覆盖总是覆盖父类中特定的现有方法签名。

您可以通过在父类中定义变体抽象方法并在必要时在子类中覆盖它来找到解决问题的方法。

【讨论】:

    【解决方案3】:

    您可以按照您的建议这样做:您可以在子类中添加一个额外的参数。

    但是这样做很可能违反substitution principle 并且可能导致错误和设计问题。通常,每次超类可用时,都希望子类可用。 也就是说,每次方法或函数需要Agent 时,您应该能够传入CleverAgent。 不幸的是,如果CleverAgent 带有额外的参数,那么任何在Agent 上调用perceive_world 的代码都会在给定CleverAgent 时失败。

    有时需要向子类添加可选参数(具有默认值的参数)。如果某些代码要知道子类的特殊行为并且该代码希望在与方法交互时利用这些知识,那么这可能是正确的方法。

    此外,在某些情况下,您正在使用子类化,而 Liskov 替换原则实际上并不是所需的属性。这种情况通常意味着您将语言的对象继承机制用于某些目的,而不是简单的子类型化。如果您发现自己处于这种情况,这是一个很好的提示,您应该反复检查您的设计,看看是否有更好的方法。

    【讨论】:

    • 在 Python 中,违反 LSP 通常是有意义的,但使用 ABC 违反它几乎不会。你会特意说“这个类确实是实现这个接口的子类型”。所以,我认为答案应该更多地关注 ABC 和抽象方法以及它们的含义,而不是泛泛而谈。
    猜你喜欢
    • 1970-01-01
    • 2013-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-31
    • 1970-01-01
    相关资源
    最近更新 更多