Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Specify return type of a wrapper function that calls an abstract method in Python

For this example, consider the simplified scenario where a Solver will return a Solution.

We have Solutions:

class Solution(ABC):
    pass


class AnalyticalSolution(Solution):
    pass


class NumericalSolution(Solution):
    def get_mesh_size(self) -> float:
        return 0.12345

And Solvers:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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_sizemember.

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?

>Solution :

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())
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading