The title might be a bit ambiguous as I'm not sure how to phrase what I am trying to do exactly.
I would like to replicate something like this pattern from C++ in python:
template<typename T>
struct Foo
{
using t = T; // Alias T so it can be used in Bar
};
template<typename T>
struct Bar
{
// The type T from Foo<T> can be used as a return type here
typename T::t fn()
{
// do something that returns T::t
}
};
int main(){
// x is determined to be a float
auto x = Bar<Foo<float>>().fn();
};
In terms of python, I would like to specialize a generic type Bar[T] with another specialized type Foo[T] and then use the type that was used to specialize Foo to type hint in Bar.
Something like
from typing import TypeVar, Generic
T = TypeVar("T")
class Foo(Generic[T]):
...
class Bar(Generic[Foo[T]]): # <-- This is illegal
def fn(self) -> T:
...
# In the following, I would like the type checker to know that x is an int.
x = Bar[Foo[int]]().fn()
I know that this relationship can be deduced when creating instances of Bar if we have something like
class Bar(Generic[T]):
def __init__(self, foo: Foo[T]):
...
but that doesn't really fit my current problem.
I would rather like to be able to create a family of specializations Foo[T], Bar[T], Baz[T], etc without having repeat T many times. In my real use case the types are more like Foo[R, S, T] and repeating the types is quite tedious and error prone and, conceptually, classes Bar, Baz, etc are thought of not as depending on a type T but rather a particular type of Foo[T].
So it would be nice to have the ability to do something like
MyFoo = Foo[int]
MyBar = Bar[MyFoo]
MyBaz = Baz[MyFoo]
Reading the mypy documentation, I came across this: Advanced uses of self-types
By using a TypeVar bounded by Foo in Bar we can specify that Bar is parameterized by something Foo-like. Then, within individual methods we can annotate self with the nested type Bar[Foo[T]] causing T to be bound to the parameter of the parameterizing Foo parameterizing Bar.
from __future__ import annotations
from typing import TypeVar, Generic
T = TypeVar("T")
class Foo(Generic[T]):
...
# You may want to make `S` covariant with `covariant=True`
# if you plan on using subclasses of `Foo` as parameters to `Bar`.
S = TypeVar("S", bound=Foo)
class Bar(Generic[S]):
def fn(self: Bar[Foo[T]]) -> T:
...
# Below, when binding `fn`, `self` is recognized as
# being of type `Bar[Foo[int]]` which binds `T` as `int`
# and hence the return type is also `int`.
x = Bar[Foo[int]]().fn()
What I have been unable to figure out is how to annotate an attribute of Bar using the parameter T in Bar[Foo[T]]. Something like
class Bar(Generic[Foo[T]]): # Still invalid.
item: T
sill doesn't work as.
However, this can be worked around by wrapping the attribute in a property
class Bar(Generic[S]):
_item: Any
@property
def item(self: Bar[Foo[T]]) -> T:
return self._item
at the cost of the type hint for _item being imprecise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With