'Python overriding type hint on a method's return in child class, without redefining method signature
I have a base class with a type hint of float on a method's return.
In the child class, without redefining the signature, can I somehow update the type hint on the method's return to be int?
Sample Code
#!/usr/bin/env python3.6
class SomeClass:
"""This class's some_method will return float."""
RET_TYPE = float
def some_method(self, some_input: str) -> float:
return self.RET_TYPE(some_input)
class SomeChildClass(SomeClass):
"""This class's some_method will return int."""
RET_TYPE = int
if __name__ == "__main__":
ret: int = SomeChildClass().some_method("42"). #
ret2: float = SomeChildClass().some_method("42")
My IDE complains about a type mismatch:
This is happening because my IDE is still using the type hint from SomeClass.some_method.
Research
I think the solution might be to use generics, but I am not sure if there's a simpler way.
Python: how to override type hint on an instance attribute in a subclass?
Suggests maybe using instance variable annotations, but I am not sure how to do that for a return type.
Solution 1:[1]
Okay so I was able to play around and combine the answers from @AntonPomieshcheko and @KevinLanguasco to come up with the a solution where:
- My IDE (PyCharm) can properly infer return type
mypyreports if a there's a mismatch of types- Doesn't error at runtime, even if type hints indicate a mismatch
This is exactly the behavior I had wanted. Thank you so much to everyone :)
#!/usr/bin/env python3
from typing import TypeVar, Generic, ClassVar, Callable
T = TypeVar("T", float, int) # types supported
class SomeBaseClass(Generic[T]):
"""This base class's some_method will return a supported type."""
RET_TYPE: ClassVar[Callable]
def some_method(self, some_input: str) -> T:
return self.RET_TYPE(some_input)
class SomeChildClass1(SomeBaseClass[float]):
"""This child class's some_method will return a float."""
RET_TYPE = float
class SomeChildClass2(SomeBaseClass[int]):
"""This child class's some_method will return an int."""
RET_TYPE = int
class SomeChildClass3(SomeBaseClass[complex]):
"""This child class's some_method will return a complex."""
RET_TYPE = complex
if __name__ == "__main__":
some_class_1_ret: float = SomeChildClass1().some_method("42")
some_class_2_ret: int = SomeChildClass2().some_method("42")
# PyCharm can infer this return is a complex. However, running mypy on
# this will report (this is desirable to me):
# error: Value of type variable "T" of "SomeBaseClass" cannot be "complex"
some_class_3_ret = SomeChildClass3().some_method("42")
print(
f"some_class_1_ret = {some_class_1_ret} of type {type(some_class_1_ret)}\n"
f"some_class_2_ret = {some_class_2_ret} of type {type(some_class_2_ret)}\n"
f"some_class_3_ret = {some_class_3_ret} of type {type(some_class_3_ret)}\n"
)
Solution 2:[2]
The following code works nicely on PyCharm. I added the complex case to make it clearer.
I basically extracted the method to a generic class and then used it as a mixin to each subclass. Please use with extra care, since it seems to be rather non-standard.
from typing import ClassVar, Generic, TypeVar, Callable
S = TypeVar('S', bound=complex)
class SomeMethodImplementor(Generic[S]):
RET_TYPE: ClassVar[Callable]
def some_method(self, some_input: str) -> S:
return self.__class__.RET_TYPE(some_input)
class SomeClass(SomeMethodImplementor[complex]):
RET_TYPE = complex
class SomeChildClass(SomeClass, SomeMethodImplementor[float]):
RET_TYPE = float
class OtherChildClass(SomeChildClass, SomeMethodImplementor[int]):
RET_TYPE = int
if __name__ == "__main__":
ret: complex = SomeClass().some_method("42")
ret2: float = SomeChildClass().some_method("42")
ret3: int = OtherChildClass().some_method("42")
print(ret, type(ret), ret2, type(ret2), ret3, type(ret3))
If you change, for example, ret2: float to ret2: int, it will correctly show a type error.
Sadly, mypy does show errors in this case (version 0.770),
otherhint.py:20: error: Incompatible types in assignment (expression has type "Type[float]", base class "SomeClass" defined the type as "Type[complex]")
otherhint.py:24: error: Incompatible types in assignment (expression has type "Type[int]", base class "SomeClass" defined the type as "Type[complex]")
otherhint.py:29: error: Incompatible types in assignment (expression has type "complex", variable has type "float")
otherhint.py:30: error: Incompatible types in assignment (expression has type "complex", variable has type "int")
The first errors can be "fixed" by writing
RET_TYPE: ClassVar[Callable] = int
for each subclass. Now, the errors reduce to
otherhint.py:29: error: Incompatible types in assignment (expression has type "complex", variable has type "float")
otherhint.py:30: error: Incompatible types in assignment (expression has type "complex", variable has type "int")
which are precisely the opposite of what we want, but if you only care about PyCharm, it doesn't really matter.
Solution 3:[3]
You can just use something like that:
from typing import TypeVar, Generic
T = TypeVar('T', float, int) # types you support
class SomeClass(Generic[T]):
"""This class's some_method will return float."""
RET_TYPE = float
def some_method(self, some_input: str) -> T:
return self.RET_TYPE(some_input)
class SomeChildClass(SomeClass[int]):
"""This class's some_method will return int."""
RET_TYPE = int
if __name__ == "__main__":
ret: int = SomeChildClass().some_method("42")
ret2: float = SomeChildClass().some_method("42")
But there is one problem. That I do not know how to solve. For SomeChildClass method some_method IDE will show generic hint. At least pycharm(I suppose you this it) does not show it as error.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Neuron - Freedom for Ukraine |
| Solution 2 | |
| Solution 3 | Anton Pomieshchenko |

