I’m looking for a way to simplify the type hints for a generic function. Specifically, I want to parametrize a type of a function, where this type itself is also parametrized, and I want to specify the latter differently for different variables. To be specific, I want to achieve something like this:
from typing import TypeVar
GenericType = TypeVar("GenericType", bound=GenericClass)
def normalized(obj: GenericType[int], factor: float) -> GenericType[float]:
# actual implementation
What you are looking for are higher kinded types, they do not yet exist in Python, so you can’t really write generic functions that preserve the container type while changing the element type.
Your overload hack is the best you can do currently without writing a mypy plugin that uses a function hook to change the return type to be more precise.
Thanks, do you know if it’s on the roadmap to be able to create higher kinded types to preserve the container type while changing the element type? If not, do you know if I should request that somewhere and where?
It’s definitely something a lot of people have asked for. But their implementation is non-trivial and some type systems have explicitly decided against adding them for that very reason. Python is not at that point yet, since nobody has gone through the trouble of writing a PEP yet as far as I’m aware. There are other more pressing topics, so I wouldn’t hold my breath, until the typing specification is in a more complete and stable place.
If you have sufficient control over GenericClass, here’s what I managed to code with returns:
from typing import Self, TypeVar, reveal_type
from returns.primitives.hkt import Kind1, SupportsKind1, kinded
_T = TypeVar('_T')
_I = TypeVar('_I', bound='_SomeContainer')
class _SomeContainer(SupportsKind1[_I, _T], list[_T]):
pass
class SomeContainer(_SomeContainer['SomeContainer', _T]):
pass
class ChildContainer(_SomeContainer['ChildContainer', _T]):
pass
@kinded
def normalized(obj: Kind1[_I, int], factor: float) -> Kind1[_I, float]:
return obj
parent: SomeContainer[int]
child: ChildContainer[int]
reveal_type(normalized(parent, 2)) # N: Revealed type is "main.SomeContainer[builtins.float]"
reveal_type(normalized(child, 2)) # N: Revealed type is "main.ChildContainer[builtins.float]"
Note that ChildContainer does not inherit from SomeContainer - I can’t figure out any way to do correct inheritance with SupportsKind types - you cannot override first type arg, and it has to be specific. However, probably inheritance from _SomeContainer should be enough in most cases, and some workarounds should exist if not.