Overriding abstract methods in python
Asked Answered
F

3

13

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)
Fumigate answered 24/4, 2018 at 18:7 Comment(6)
Well, what happens if you try it?Graphic
While you can, please don't. It's going to be confusing and bug-prone. People are going to expect agents to have a 1-parameter perceive_world method.Kranz
If you want this to be at all useful, you probably want the abstract method to take the extra parameter (maybe with a default value, and maybe even with classes that don’t want that parameter asserting that they got the default value). Otherwise, knowing that an object meets the Agent ABC doesn’t actually tell you how to use it, so why even have an ABC?Antirrhinum
Or, alternatively, just make Clever_Agent give a default value for the extra argument and have it do something reasonable if the default is passed.Antirrhinum
I don't understand why this got so many downvotes? In a language like Java, you can method overload. So it's no issue. I just want to know what an alternative in Python would be?Fumigate
@Fumigate In a language like Java, this would not count as an implementation of the interface method. In Python, it _would_—but only accidentally.Antirrhinum
A
24

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 Agents", 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)
Antirrhinum answered 24/4, 2018 at 19:5 Comment(1)
@abarmert, I have the same question as this. After reading your answer, my understanding is that it is reasonable to add extra parameter in the subclass method. But it seems that the first part of your answer does not support this kind of mechanism. I am a little bit confusing about this.Toughen
C
0

In many ways overriding an abstract method from a parent class and adding or changing the method signature is technically not called a method override what you may be effectively be doing is method hiding. Method override always overrides a specific existing method signature in the parent class.

You may find your way around the problem by defining a variant abstract method in your parent class, and overriding it if necessary in your sub classes.

Casual answered 24/4, 2018 at 18:14 Comment(0)
G
0

You can do so simply by doing just as you proposed: you can add an extra parameter in a subclass.

However doing so is very likely to violate the substitution principle and is likely to lead to bugs and design problems. In general, it is desirable that a subclass be usable every time a superclass is usable. That is, every time a method or function wants an Agent, you should be able to pass in a CleverAgent. Unfortunately, if CleverAgent takes additional parameters, then any code that calls perceive_world on an Agent will fail when given a CleverAgent.

It is sometimes desirable to add an optional paremeter (one with a default value) to a subclass. This can be the right approach if some code is going to be aware of the subclass's special behavior and if that code wants to take advantage of that knowledge when interacting with a method.

Also, there are some cases where you're using subclassing and where the Liskov Substitution Principle is not actually a desired property. Such cases typically mean that you're using the language's object inheritance mechanism for some purpose other than simple subtyping. If you find yourself in such a situation, it's a good hint that you should tripple-check your design and see if there is an approach that is better aligned.

Gant answered 24/4, 2018 at 18:14 Comment(1)
In Python, violating the LSP often makes sense—but violating it with an ABC almost never does. You’re going out of your way to say “This class really is a subtype that implements this interface”. So, I think the answer should focus more on ABC and abstractmethod and what they’re intended to mean, rather than generalities.Antirrhinum

© 2022 - 2024 — McMap. All rights reserved.