dmmpie
dmmpie

Reputation: 445

mypy error for setup with inheritance of function that depends on class function defined differently in both classes

I have a setup that looks as following:

from typing import TypeVar

MyType = TypeVar('MyType', bound='TestClass')

class TestClass():
    def __init__(self, number):
        self.number = number

    def __mul__(self, multiplicator: int) -> 'TestClass':
        return TestClass(self.number*multiplicator)

    def test_mul(self: MyType, multiplicator: int) -> MyType:
        return self*multiplicator

class InheritingClass(TestClass):

    def __mul__(self, multiplicator: int) -> 'InheritingClass':
        return InheritingClass(self.number*multiplicator+1)

Evaluating this with mypy returns the error Incompatible return value type (got "TestClass", expected "MyType") in the line return self*multiplicator in TestClass.test_mul

The goal here is to ultimately have two different objects, where one inherits the properties of the other, and have them use the same test_mul function while making use of a different __mul__ function. I want to be able to call the test_mul function on either class, with the respective __mul__ functions of each class being called accordingly. Is there a way to achieve that without altering the way a new instance of the own class is returned in each __mul__ function? If not, how would an alternative look here?

Upvotes: 0

Views: 485

Answers (2)

hussic
hussic

Reputation: 1920

The problem is in __mul__ in TestClass: it should return MyType and use type(self).

MyType = TypeVar('MyType', bound='TestClass')


class TestClass():

    def __init__(self, number):
        self.number = number

    def __mul__(self: MyType, multiplicator: int) -> MyType:
        return type(self)(self.number * multiplicator)

    def test_mul(self: MyType, multiplicator: int) -> MyType:
        x: MyType = self * multiplicator
        return x


class InheritingClass(TestClass):

    def __mul__(self, multiplicator: int) -> 'InheritingClass':
        return InheritingClass(self.number * multiplicator + 1)


tc = TestClass(1)
tcx = tc * 3
tcy = tc.test_mul(4)
if TYPE_CHECKING:
    # Mypy: Revealed type is "Tuple[TestClass*, TestClass*]"
    reveal_type((tcx, tcy))
ic = InheritingClass(2)
icx = ic * 3
icy = ic.test_mul(4)
if TYPE_CHECKING:
    # Mypy: Revealed type is "Tuple[InheritingClass, InheritingClass*]"
    reveal_type((icx, icy))

Upvotes: 1

Numerlor
Numerlor

Reputation: 839

A cast to MyType in the test_mul method should work:

from typing import TypeVar, cast
MyType = TypeVar('MyType', bound='TestClass')


class TestClass():
    def __init__(self, number):
        self.number = number

    def __mul__(self, multiplicator: int) -> 'TestClass':
        return TestClass(self.number*multiplicator)

    def test_mul(self: MyType, multiplicator: int) -> MyType:
        return cast(MyType, self*multiplicator)


class InheritingClass(TestClass):

    def __mul__(self, multiplicator: int) -> 'InheritingClass':
        return InheritingClass(self.number*multiplicator+1)

reveal_type(TestClass(5).test_mul(5))
reveal_type(InheritingClass(5).test_mul(5))

gives us

file.py:21: note: Revealed type is "file.TestClass*"
file.py:22: note: Revealed type is "file.InheritingClass*"

When mypy support for it comes, using the Self type (planned for 3.11, currently in typing-extensions) as the return annotation for the test_mul method should also work. The current PR that plans to add is can be found here https://github.com/python/mypy/pull/11666

Upvotes: 3

Related Questions