Pretty good!

Overall you have reasonable types, especially Array (though that should only be declared once). But your use of kwargs and setattr interferes with that type safety. There are type-safe ways around this: one way would be for Rational to be a @dataclass, and for its super to call into fields() or asdict(). Or without dataclasses, maybe __dict__ would work (so long as Rational stores offset, enumerator and denominator as members from its own constructor).

Though it's unlikely for eval to collide with the built-in eval, it's enough to confuse some syntax highlighters etc. Probably best to rename this.

This:

    powers_of_x = [x**i for i in range(len(self.enumerator))]

can be vectorised as

    powers_of_x = x ** np.arange(len(self.enumerator))

The __main__ guard is not enough to exclude P, etc. from the global namespace. That code should be moved to a main function.

Don't assert in production code; raise an exception instead.

Answer from Reinderien on Stack Exchange
🌐
Reddit
reddit.com › r/python › what's the point of abstract classes if they don't enforce method signatures?
r/Python on Reddit: What's the point of abstract classes if they don't enforce method signatures?
December 18, 2016 -

I was surprised to see the Python abstract classes don't enforce anything except the override and method name. I can see why in Python enforcing parameter data-types would probably not work, but the number of parameters and parameter names ought to be enforced.

I've always thought the point of abstract classes was to ensure that any inheritor of the class would would work with existing code to run the abstract methods defined in the super class. The whole point was to enforce method signatures.

It seems to me that Python's implantation of abstract classes has very little utility. Does anyone even use them? What for?

Discussions

Abstract method with differing concrete signatures
The connection can currently be over serial or TCP, with others possibly added in the future. The base class ConnectionHandler is abstract. Children must implement a connect method (among others), but the signature of this method necessarily differs between connection types. More on discuss.python.org
🌐 discuss.python.org
1
0
February 17, 2025
Signature mismatch issues when using @abstractmethod
TL;DR comment - There is no error thrown when a subclass implements an abstract method but uses too many/few arguments and the subclass method does not include any type annotations in its signature comment - When a subclass implements an... More on github.com
🌐 github.com
3
May 1, 2020
Overriding abstract methods in python - Stack Overflow
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 More on stackoverflow.com
🌐 stackoverflow.com
python - Abstract classes with varying amounts of parameters - Stack Overflow
I would say that you should not ... not look pythonic from my perspective. If a method has different signature in every descendant you should extract common functionality and reorganise logic. But its just my personal opinion. If you show the code I can say more 2022-06-14T12:17:56.7Z+00:00 ... Mhh. with current provided answer I still get type hinting errors Also, is not really SOLID because, in a way the Abstract class is still ... More on stackoverflow.com
🌐 stackoverflow.com
🌐
GitHub
github.com › python › mypy › issues › 8966
Signature mismatch issues when using @abstractmethod · Issue #8966 · python/mypy
May 1, 2020 - from abc import ABC, abstractmethod class Interface(ABC): @abstractmethod def abstract_method(self, a: int) -> str: raise NotImplementedError class Concrete(Interface): def abstract_method(self): # Signature mismatch return 123
Author   Kangaroux
🌐
Python
bugs.python.org › issue20897
Issue 20897: @abstractmethod does not enforce method signatures - Python tracker
This issue tracker has been migrated to GitHub, and is currently read-only. For more information, see the GitHub FAQs in the Python's Developer Guide · This issue has been migrated to GitHub: https://github.com/python/cpython/issues/65096
🌐
Medium
medium.com › @aaron_imn › enforce-abstract-methods-signature-to-derived-classes-in-python-a21414943ce6
Enforce abstract methods signature to derived classes in Python | Medium
February 26, 2024 - In this story, throughout an example, I will explain how to enforce the derived class of an abstract base class in Python to follow the same parameter types and names of the abstract methods’ parameters. In the example below, an abstract base class A has been defined that has an abstractmethod called a. The classes B and C have derived from A, whereas B follows the same signature of the abstract method a, while C does not.
🌐
Narkive
comp.lang.python.narkive.com › 07geq6I5 › abc-with-abstractmethod-kwargs-on-base-explicit-names-on-implementation
ABC with abstractmethod: kwargs on Base, explicit names on implementation
January 22, 2025 - In a call, all those arguments must be passed as "name=value". In your case above, `b` is not a keyword argument and thus is not matched by `**kwargs`. The error you observe is justified. @abstractmethod ... ... Note that the base method signature allows arbitrary positional and keyword arguments.
Find elsewhere
Top answer
1 of 3
28

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)
2 of 3
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.

🌐
PyPI
pypi.org › project › abcmeta
abcmeta · PyPI
May 2, 2025 - Note: abcmeta supports Python3.6+. ... name, age): pass · If you put a different signature, it will raise an error with 'diff' format with hints about what you've missed:...
      » pip install abcmeta
    
Published   Nov 03, 2024
Version   2.2.1
🌐
JetBrains
youtrack.jetbrains.com › issue › PY-23513 › Overrides-of-abstract-static-methods-with-expanded-arguments-fail-signature-checking-PyMethodOverriding
Overrides of abstract static methods with expanded ...
October 28, 2024 - {{ (>_<) }} This version of your browser is not supported. Try upgrading to the latest stable version. Something went seriously wrong
🌐
Python
docs.python.org › 3 › library › collections.abc.html
collections.abc — Abstract Base Classes for Containers
If the Set mixin is being used in a class with a different constructor signature, you will need to override _from_iterable() with a classmethod or regular method that can construct new instances from an iterable argument.
Top answer
1 of 2
1

Giving an override a more restrictive signature violates the Liskov Substitution Principle. But that's not what you should be doing here.

Your code should actually look like this:

from abc import ABC, abstractmethod

class Manager(ABC):
    @abstractmethod
    def connect(self) -> Handle:
        raise NotImplementedError()

class DefaultManager(Manager):
    def connect(self, *, thread_safe: bool = False) -> Handle:
        if thread_safe:
            return ThreadSafeHandle()
        else:
            return DefaultHandle()

(I'm assuming you've got something appropriate to annotate the return types with.)

Manager.connect's signature should reflect the kinds of calls that all subclass connect implementations should accept. It should be the intersection of all valid subclass signatures, not the union.

The signature specified by Manager.connect reflects what Manager promises all Manager instances can do. It promises that they can accept a connect call with 0 arguments. It doesn't promise anything about what they can't do. This Manager.connect signature doesn't accept a thread_safe argument, but that doesn't mean it promises subclasses won't accept that argument.

2 of 2
1

Your first example would be completely fine, as everything the abstract parent accepts is also accepted by the derived class, the argument types are identical (<s>a</s>, <s>b</s> untyped). See comment for why the first snippet isn't quite type-correct, only an override with def foo(self, a:Any=default, b:Any=default, *args, **kwargs): would be correct.


To be type-correct, the type of the accepted arguments has to get broader, not narrower. So I would take issue with the concrete example, since using a DefaultManager as a Manager would imply one could pass any amount of positional arguments to it, and any value into thread_safe.

More concretely, IMO, the best practice here is this:

from abc import ABC, abstractmethod

class Manager(ABC):
    @abstractmethod
    def connect(self):
        raise NotImplementedError()

class DefaultManager(Manager):
    def connect(self, *, thread_safe: bool = False):
        if thread_safe:
            return ThreadSafeHandle()
        else:
            return DefaultHandle()

because everything Manager.connect accepts is also accepted by DefaultManager.connect

🌐
Python.org
discuss.python.org › typing
Enforcing __init__ signature when implementing it as an abstractmethod - Typing - Discussions on Python.org
December 29, 2024 - Hello. I noticed that Pyright doesn’t check the signatures of abstractmethod implementations when the abstractmethod is __init__(). Here’s an example: from abc import ABC, abstractmethod class AbstractA(ABC): @abstractmethod def __init__(self, x: int, y: int): pass @abstractmethod def do_something(self, z: int, u: int): pass class RealA(AbstractA): def __init__(self, x: int): ## No static type checker error self.x = x def do_something...
🌐
Python
peps.python.org › pep-3119
PEP 3119 – Introducing Abstract Base Classes | peps.python.org
July 26, 2025 - A subclass of Iterable, Sized, Container. It defines a new abstract method __getitem__ that has a somewhat complicated signature: when called with an integer, it returns an element of the sequence or raises IndexError; when called with a slice ...
🌐
Justin A. Ellis
jellis18.github.io › post › 2022-01-11-abc-vs-protocol
Abstract Base Classes and Protocols: What Are They? When To Use Them?? Lets Find Out! - Justin A. Ellis
January 11, 2022 - In this case a static type checker (Pylance in the example above) would raise an error and tell the user that Dog does obey the Animal Protocol since it doesn't implement the speak method. Notice that this is different than an ABC that will raise an error when creating the Dog class, whereas Protocols will raise an error where they are used. Ok, lets get a bit more tricky. What if we define the Dog class but change the signature of speak a bit
🌐
Python
docs.python.org › 3 › library › abc.html
abc — Abstract Base Classes
Dynamically adding abstract methods to a class, or attempting to modify the abstraction status of a method or class once it is created, are only supported using the update_abstractmethods() function. The abstractmethod() only affects subclasses derived using regular inheritance; “virtual subclasses” registered with the ABC’s register() method are not affected.
🌐
The Teclado Blog
blog.teclado.com › python-abc-abstract-base-classes
How to Write Cleaner Python Code Using Abstract Classes
October 26, 2022 - The problem is that every class has a different method name, when feeding a lion it's give_food(), when feeding a panda it's feed_animal() and it's feed_snake() for a snake. This code is a mess because methods that do the same thing should be named the same. If we could only force our classes to implement the same method names... It turns out that the Abstract class is what we need.