How can I type hint that the return type of a method of some abstract class is some instance of this class?
My intuitive answer is that
@abstractmethod
def mymethod() -> typing.Self:
pass
should be the correct way (as suggested in this post for example). But when I now subclass from this class, the return type is restricted to the type of the child class. What is the correct way to type hint here, so every subclass of the parent class is allowed?
Example code:
from abc import ABC, abstractmethod
from typing import Self, override
import random
class DiceResult(ABC):
@abstractmethod
def reroll(self) -> Self:
pass
class NaturalTwenty(DiceResult):
@override
def reroll(self) -> DiceResult: # Type checkers report an error here
return random.choice([NaturalTwenty(), NaturalOne()])
class NaturalOne(DiceResult):
@override
def reroll(self) -> DiceResult: # Type checkers report an error here
return random.choice([NaturalTwenty(), NaturalOne()])
@Schwern already explains why typing.Self
does not work here in their answer.
This is because typing.Self
always type hints the current enclosing class. If it is the subclass of another class, the type hint refers to the subclass.
To solve this, we want to use the parent class as type annotation, as in
class DiceResult(ABC):
@abstractmethod
def reroll(self) -> DiceResult:
pass
This does not work in the current python version though, as it is a forward reference. The current solution is to use a string literal instead, as in
class DiceResult(ABC):
@abstractmethod
def reroll(self) -> "DiceResult":
pass
In the future, the first version is intended to work as well, and is also already (python >= 3.7) available in __future__
. It can be used as
from __future__ import annotations
class DiceResult(ABC):
@abstractmethod
def reroll(self) -> DiceResult:
pass