Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using an Argument to a Generic Type in Another Generic Type

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]
like image 989
Ryan Avatar asked Sep 06 '25 07:09

Ryan


1 Answers

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.

like image 53
Ryan Avatar answered Sep 07 '25 21:09

Ryan