For this example, consider the simplified scenario where a Solver
will return a Solution
.
We have Solution
s:
class Solution(ABC):
pass
class AnalyticalSolution(Solution):
pass
class NumericalSolution(Solution):
def get_mesh_size(self) -> float:
return 0.12345
And Solver
s:
class Solver(ABC):
def solve(self, task: int) -> Solution:
# Do some pre-processing with task
# ...
return self._solve(task)
@abstractmethod
def _solve(self, task: int) -> Solution:
pass
class NumericalSolver(Solver):
def _solve(self, task: int) -> NumericalSolution:
return NumericalSolution()
class AnalyticalSolver(Solver):
def _solve(self, task: int) -> AnalyticalSolution:
return AnalyticalSolution()
The problem I encounter results from the implementation of the wrapper method solve
that then calls the abstract method _solve
.
I often encounter a situation like this where I want to do some preprocessing in the solve
method that is the same for all solver, but then the actual implementation of _solve
might differ.
If I now call the numerical solver and call the get_mesh_size()
method, Pylance (correctly) tells me that a Solution
object has no get_mesh_size
member.
if __name__ == "__main__":
solver = NumericalSolver()
solution = solver.solve(1)
print(solution.get_mesh_size())
I understand that Pylance only sees the interface of solve
which indicates that the return type is a Solution
object that does not need to have a get_mesh_size
method.
I am also aware that this example works at runtime.
I tried to use TypeVar
like this (actually, because ChatGPT suggested it):
class Solution(ABC):
pass
T = TypeVar("T", bound=Solution)
and then rewrite the Solver
class:
class Solver(ABC):
def solve(self, task: int) -> T:
# Do some pre-processing with task
# ...
return self._solve(task)
@abstractmethod
def _solve(self, task: int) -> T:
pass
But Pylance now tells me TypeVar "T" appears only once in generic function signature
. So this can't be the solution.
How do I get typing to work with this example?
CodePudding user response:
You may use Generic[T]
as a base for Solver
and then extend it as follows
from abc import ABC, abstractmethod
from typing import TypeVar, Generic
class Solution(ABC):
pass
class AnalyticalSolution(Solution):
pass
class NumericalSolution(Solution):
def get_mesh_size(self) -> float:
return 0.12345
SolutionGeneric = TypeVar("SolutionGeneric", bound=Solution)
class Solver(ABC, Generic[SolutionGeneric]):
def solve(self, task: int) -> SolutionGeneric:
# Do some pre-processing with task
# ...
return self._solve(task)
@abstractmethod
def _solve(self, task: int) -> SolutionGeneric:
pass
class NumericalSolver(Solver[NumericalSolution]):
def _solve(self, task: int) -> NumericalSolution:
return NumericalSolution()
class AnalyticalSolver(Solver):
def _solve(self, task: int) -> AnalyticalSolution:
return AnalyticalSolution()
if __name__ == "__main__":
solver = NumericalSolver()
solution = solver.solve(1)
print(solution.get_mesh_size())