覆盖 python 中的抽象方法
Overriding abstract methods in python
重写 python 抽象方法时,有没有办法在方法签名中使用额外参数重写该方法?
例如
摘要class=
Agent(ABC):
@abstractmethod
def perceive_world(self, observation):
pass
继承class:
Dumb_agent(Agent):
def perceive_world(self, observation):
print('I see %s' % observation)
在方法签名中使用额外参数继承 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)
在很多方面覆盖父类的抽象方法 class 并添加或更改方法签名在技术上不称为 方法覆盖 你可能实际上是做的是方法隐藏。方法覆盖始终覆盖父级中特定的现有方法签名 class。
您可以通过在父 class 中定义变体抽象方法并在必要时在子 class 中覆盖它来找到解决问题的方法。
您可以按照您的建议简单地做到这一点:您可以在子类中添加一个额外的参数。
然而这样做很可能会违反substitution principle并且很可能会导致错误和设计问题。通常,希望每次超类可用时子类也可用。
也就是说,每次方法或函数需要 Agent
时,您应该能够传入 CleverAgent
。
不幸的是,如果 CleverAgent
接受额外的参数,那么任何在 Agent
上调用 perceive_world
的代码在给定 CleverAgent
.
时都会失败
有时需要向子类添加可选参数(具有默认值的参数)。如果某些代码要知道子类的特殊行为,并且如果该代码想在与方法交互时利用该知识,这可能是正确的方法。
此外,在某些情况下,您正在使用子类化,而 Liskov 替换原则实际上并不是所需要的 属性。这种情况通常意味着您将语言的对象继承机制用于简单子类型化以外的其他目的。如果您发现自己处于这种情况,这是一个很好的提示,您应该仔细检查您的设计,看看是否有更合适的方法。
您尝试做的事情会奏效 — 但这是一个非常糟糕的主意。
一般来说,您不希望在覆盖时以不兼容的方式更改方法的签名。这是 Liskov Substitution Principle.
的一部分
在 Python 中,通常有充分的理由违反这一点——继承并不总是与子类型有关。
但是,当您使用 ABC 定义接口时,这显然是关于子类型化的。这是 ABC
子类和 abstractmethod
装饰器的唯一目的,因此使用它们来表示其他任何东西充其量是高度误导的。
更详细:
通过从 Agent
继承,您声明 Clever_Agent
的任何实例都可以像 Agent
一样使用。这包括能够调用 my_clever_agent.perceive_world(my_observation)
。事实上,它不只是 包括 那个;这就是它的全部含义!如果那个调用总是失败,那么没有 Clever_Agent
是 Agent
,所以它不应该声称是。
在某些语言中,您有时需要假装绕过接口检查,这样您以后可以通过类型切换 and/or "dynamic-cast" 回到实际类型。但在 Python 中,这根本没有必要。没有 "a list of Agent
s" 这样的东西,只是一个什么都没有的列表。 (除非您正在使用可选的静态类型检查——但在那种情况下,如果您需要绕过静态类型检查,请不要声明静态类型只是为了给自己设置障碍。)
在 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)
重写 python 抽象方法时,有没有办法在方法签名中使用额外参数重写该方法?
例如
摘要class=
Agent(ABC):
@abstractmethod
def perceive_world(self, observation):
pass
继承class:
Dumb_agent(Agent):
def perceive_world(self, observation):
print('I see %s' % observation)
在方法签名中使用额外参数继承 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)
在很多方面覆盖父类的抽象方法 class 并添加或更改方法签名在技术上不称为 方法覆盖 你可能实际上是做的是方法隐藏。方法覆盖始终覆盖父级中特定的现有方法签名 class。
您可以通过在父 class 中定义变体抽象方法并在必要时在子 class 中覆盖它来找到解决问题的方法。
您可以按照您的建议简单地做到这一点:您可以在子类中添加一个额外的参数。
然而这样做很可能会违反substitution principle并且很可能会导致错误和设计问题。通常,希望每次超类可用时子类也可用。
也就是说,每次方法或函数需要 Agent
时,您应该能够传入 CleverAgent
。
不幸的是,如果 CleverAgent
接受额外的参数,那么任何在 Agent
上调用 perceive_world
的代码在给定 CleverAgent
.
有时需要向子类添加可选参数(具有默认值的参数)。如果某些代码要知道子类的特殊行为,并且如果该代码想在与方法交互时利用该知识,这可能是正确的方法。
此外,在某些情况下,您正在使用子类化,而 Liskov 替换原则实际上并不是所需要的 属性。这种情况通常意味着您将语言的对象继承机制用于简单子类型化以外的其他目的。如果您发现自己处于这种情况,这是一个很好的提示,您应该仔细检查您的设计,看看是否有更合适的方法。
您尝试做的事情会奏效 — 但这是一个非常糟糕的主意。
一般来说,您不希望在覆盖时以不兼容的方式更改方法的签名。这是 Liskov Substitution Principle.
的一部分在 Python 中,通常有充分的理由违反这一点——继承并不总是与子类型有关。
但是,当您使用 ABC 定义接口时,这显然是关于子类型化的。这是 ABC
子类和 abstractmethod
装饰器的唯一目的,因此使用它们来表示其他任何东西充其量是高度误导的。
更详细:
通过从 Agent
继承,您声明 Clever_Agent
的任何实例都可以像 Agent
一样使用。这包括能够调用 my_clever_agent.perceive_world(my_observation)
。事实上,它不只是 包括 那个;这就是它的全部含义!如果那个调用总是失败,那么没有 Clever_Agent
是 Agent
,所以它不应该声称是。
在某些语言中,您有时需要假装绕过接口检查,这样您以后可以通过类型切换 and/or "dynamic-cast" 回到实际类型。但在 Python 中,这根本没有必要。没有 "a list of Agent
s" 这样的东西,只是一个什么都没有的列表。 (除非您正在使用可选的静态类型检查——但在那种情况下,如果您需要绕过静态类型检查,请不要声明静态类型只是为了给自己设置障碍。)
在 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)