When overriding a python abstract method, is there a way to override the method with extra parameters in the method signature?
e.g.
The abstract class =
Agent(ABC):
@abstractmethod
def perceive_world(self, observation):
pass
The inheriting class:
Dumb_agent(Agent):
def perceive_world(self, observation):
print('I see %s' % observation)
Inheriting class with an extra parameter in the method signature:
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)
What you're trying to do will just work—but it's a very bad idea.
In general, you don't want to change the signature of a method in incompatible ways when overriding. That's part of the Liskov Substitution Principle.
In Python, there are often good reasons to violate that—inheritance isn't always about subtyping.
But when you're using ABCs to define an interface, that's explicitly about subtyping. That's the sole purpose of ABC
subclasses and abstractmethod
decorators, so using them to mean anything else is at best highly misleading.
In more detail:
By inheriting from Agent
, you are declaring that any instance of Clever_Agent
can be used as if it were an Agent
. That includes being able to call my_clever_agent.perceive_world(my_observation)
. In fact, it doesn't just include that; that's the entirely of what it means! If that call will always fail, then no Clever_Agent
is an Agent
, so it shouldn't claim to be.
In some languages, you occasionally need to fake your way around interface checking, so you can later type-switch and/or "dynamic-cast" back to the actual type. But in Python, that's never necessary. There's no such thing as "a list of Agent
s", just a list of anything-at-alls. (Unless you're using optional static type checking—but in that case, if you need to get around the static type checking, don't declare a static type just to give yourself a hurdle to get around.)
In Python, you can extend a method beyond its superclass method by adding optional parameters, and that's perfectly valid, because it's still compatible with the explicitly-declared type. For example, this would be a perfectly reasonable thing to do:
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)
Or even this might be reasonable:
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)