I'm trying to define a generic base class that can instantiate wrapped instances of itself like so
from typing import Callable, TypeVar, Generic
T = TypeVar("T")
U = TypeVar("U")
class From(Generic[T]):
def __init__(self, val: T):
self.val = val
@classmethod
def unit(cls, val):
return cls(val)
def bind(self, func: Callable[[T], U]):
return self.unit(func(self.val))
Basically, when calling bind on a From instance with a function that changes the type, e.g.
def to_string(i: int) -> str:
return str(i)
From(1).bind(to_string) # here, self.val = "1"
The type checker will complain because cls in From.unit is already parametrized by T (hence passing U to From[T] raises that U is not assignable to T )
You can overcome this problem by returning a parametrized instance of From in the unit method like so:
class From(Generic[T]):
...
@classmethod
def unit(cls, val: U) -> "From[U]":
return From[U](val)
which works like a charm.
However, the problem occurs when I let another class inherit from From. I lose generality:
class Just(From[T]):
pass
Just(1).bind(to_string) # equals From("1"), instead of Just("1")
Not accepting the solution that you should define a new unit method with the same form and class name on every inheriting instance, is there some way to re-parametrize the type of the cls variable in the original unit method?
I know you can use cls.__orig_bases__ to find (Generic[~T]) if you print it inside the unit method.
So I thought maybe you can do something like setattr(cls, "__orig_bases__", (Generic[U],))
But this seems senseless and in case is not picked up by the type checker.
Ideally there should be some method on self that allows you to access the unparametrized base class, so you could self.<get_baseclass>[U](<val>) .
Right now my solution is adding # type: ignore in unit like so:
@classmethod
def unit(cls, val: U) -> "From[U]":
"""Return a new instance of the same encapsulating class, wrapping `val`"""
return cls(val) # type: ignore
But this is not ideal.
-
This is a common question; you need a higher kinded type, but there's no such thing in Python's type system just yet. There should be a few duplicates somewhere.InSync– InSync2024年12月12日 19:06:42 +00:00Commented Dec 12, 2024 at 19:06
1 Answer 1
Use Self as the return type for both unit and bind.
class From(Generic[T]):
def __init__(self, val: T):
self.val = val
@classmethod
def unit(cls, val) -> Self: # returns an *instance* of cls
return cls(val)
def bind(self, func: Callable[[T], U]) -> Self:
return self.unit(func(self.val))
Now reveal_type(Just(1).bind(to_string)) returns Just[int] as desired.