Использование аргумента универсального типа в другом универсальном типе

#python #type-hinting #python-typing

Вопрос:

Название может быть немного двусмысленным, так как я не уверен, как точно сформулировать то, что я пытаюсь сделать.

Я хотел бы воспроизвести что-то подобное этому шаблону из C в 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();
};
 

С точки зрения python, я хотел бы специализировать универсальный тип Bar[T] на другом специализированном типе Foo[T] , а затем использовать тип, который использовался для специализации Foo , для ввода подсказки Bar .

Что-то вроде

 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()
 

Я знаю, что эта связь может быть выведена при создании экземпляров Bar , если у нас есть что-то вроде

 class Bar(Generic[T]):
    def __init__(self, foo: Foo[T]):
        ...
 

но это не совсем соответствует моей нынешней проблеме.

Я бы предпочел иметь возможность создавать семейство специализаций Foo[T] , Bar[T] , Baz[T] и т. Д. , Не повторяясь T много раз. В моем реальном случае использования типы больше похожи Foo[R, S, T] , и повторение типов довольно утомительно и подвержено ошибкам, и концептуально классы Bar и Baz т. Д. Рассматриваются Не как зависящие от типа T , а скорее от конкретного типа Foo[T] .

Так что было бы неплохо иметь возможность делать что-то вроде

 MyFoo = Foo[int]
MyBar = Bar[MyFoo]
MyBaz = Baz[MyFoo]
 

Комментарии:

1. Вы можете попробовать использовать typing.Union , например, Union[int, str]

2. К сожалению, я не думаю, что это возможно с текущим синтаксисом ввода Python.

Ответ №1:

Читая документацию mypy, я наткнулся на следующее: Расширенное использование самотипов

Используя TypeVar ограничение Foo in Bar , мы можем указать, что Bar оно параметризовано чем Foo -то подобным. Затем в рамках отдельных методов мы можем аннотировать self вложенный тип Bar[Foo[T]] , вызывающий T привязку к параметру параметризации Foo параметризации 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()
 

Чего я не смог понять, так это как аннотировать атрибут Bar с использованием параметра T in Bar[Foo[T]] . Что-то вроде

 class Bar(Generic[Foo[T]]):  # Still invalid.
    item: T
 

силл не работает как.

Однако это можно обойти, заключив атрибут в свойство

 class Bar(Generic[S]):
    _item: Any
    
    @property
    def item(self: Bar[Foo[T]]) -> T:
        return self._item
 

ценой подсказки типа за _item неточность.