Subclassing in python: restricting the signature of child class functions

Question:

Let’s say I have a base class like


class Foo:
    def __init__(self):
        return

    def foo(self, a: str | int) -> float:
        raise NotImplementedError()

which gets inherited by


class FooChild(Foo):
    def __init__(self):
        return

    def foo(self, a: str) -> float:
        return 0.5

Now, mypy is complaining about the Liskov substitution principle (which I might have studied but surely I have forgot) and I understand I’m violating some grounded OOP rule here. At the end of the day I just want to specify a parent class whose functions may be of different types, leaving to the children the burden of dealing with the nitty-gritty details.

So ideally this function foo should accept a large union of types, which should then be specified by the inheriting child classes. I’d like to keep this as elegant (i.e. short) as possible.

Do you know any workaround?

I tried using the @overload decorator with multiple functions like:


class Foo:
    def __init__(self):
        return
    
    @overload
    def fun(a: str) -> float:
        ...

    @overload
    def fun(a: int) -> float:
        ...

    def fun(a: str | int) -> float:
        raise NotImplementedError()

and defining ad-hoc TypeVar like YoYo = TypeVar("YoYo", str, int) but MyPy always complains, each time for a different reason.

Asked By: jb_

||

Answers:

Use a type variable:

import typing

T = typing.TypeVar("T")


class Foo(typing.Generic[T]):
    def foo(self, a: T) -> float:
        raise NotImplementedError()


class FooChild(Foo[str]):
    def foo(self, a: str) -> float:
        return 0.5


x = FooChild().foo('foo')

You could always restrict the type var as well,

T = typing.TypeVar("T", float, str)

If you wanted to restrict subclasses to using specific types.

import typing

T = typing.TypeVar("T", str, int)


class Foo(typing.Generic[T]):
    def foo(self, a: T) -> float:
        raise NotImplementedError()


class Bar(Foo[str]):
    def foo(self, a: str) -> float:
        return 0.5

class Baz(Foo[list[int]]):
    def foo(self, a: list[int]) -> float:
        return 0.1

Then mypy complains on the definition of Baz:

jarrivillaga$ mypy test_mypy.py 
test_mypy.py:15: error: Value of type variable "T" of "Foo" cannot be "list"  [type-var]
Found 1 error in 1 file (checked 1 source file)
Answered By: juanpa.arrivillaga
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.