Использование TypeVar не будет работать здесь. Так как ваш TypeVar не ограничен (не объявляет себя ограниченным одним или несколькими типами), вы можете заменить его любым произвольным типом - object, int, str, float, Dict [str, List [str]] ...
Это означает, что с вашей текущей сигнатурой типа ввода тип возвращаемого значения невыразим: с учетом входных данных иногда просто просто нет действительного типа возвращаемого значения.
В более широком смысле, независимо от того, используется ли ваш инструмент способен выводить тип возвращаемого значения на самом деле не имеет значения здесь. Система типов PEP 484 не может позволить вам объявить возвращаемый тип, который основан исключительно на чем-то внутри тела функции. Вместо этого вы должны либо объявить, что возвращаемый тип является чем-то фиксированным и конкретным (например, int или str), либо типом generi c, который каким-либо образом связан с вводом.
Есть два различных способа сделать последнее.
Во-первых, вы можете настаивать на том, что любой класс с методом op()
наследуется от некоторого класса, и этот класс должен выводить op
быть родовым c. Например:
from typing import Generic, TypeVar
TOp = TypeVar('TOp')
class Operation(Generic[TOp]):
def op(self) -> TOp: pass
class SomeClass(Operation[int]):
def op(self) -> int:
# ...snip...
return 4
class OtherClass(Operation[str]):
def op(self) -> str:
# ...snip...
return "output"
# Or alternatively, reuse the TOp variable from above -- it would mean the
# exact same thing, since TypeVars are only placeholders.
T = TypeVar('T')
def foo(arg: Operation[T]) -> T:
return arg.op()
# Deduces the output types should be int and str respectively
a = foo(SomeClass())
b = foo(OtherClass())
Наложение этих ограничений на arg
, в свою очередь, позволяет нам определить тип возвращаемого значения.
Единственным недостатком этого подхода является то, что изменение SomeClass и OtherClass может возможно быть немного навязчивым. Если вы предпочитаете не делать этого, вместо этого вы можете создать собственный Протокол :
# Or, if you need to use older versions of Python, pip-install typing-extensions
# and do 'from typing_extensions import Protocol'.
from typing import Generic, Protocol, TypeVar
TOp = TypeVar('TOp')
# Doing 'class Blah(Protocol[T])' is a shorthand for
# doing 'class Blah(Protocol, Generic[T])'.
class SupportsOp(Protocol[TOp]):
def op(self) -> TOp: ...
# SomeClass and OtherClass do *not* inherit SupportsOp!
class SomeClass:
def op(self) -> int:
# ...snip...
return 4
class OtherClass:
def op(self) -> str:
# ...snip...
return "output"
# Or alternatively, reuse the TOp variable from above -- it would mean the
# exact same thing, since TypeVars are only placeholders.
T = TypeVar('T')
def foo(arg: SupportsOp[T]) -> T:
return arg.op()
# Still deduces the output types should be int and str respectively
a = foo(SomeClass())
b = foo(OtherClass())
Основное отличие состоит в том, что протоколы используют структурный подтип . номинальный подтип . С номинальным подтипом тип A считается подтипом B, если A явно наследуется от B. Со структурным подтипом A является подтипом B, если его методы соответствуют сигнатурам методов, приведенным в B.
Конечно, будет ли ваша IDE способна выполнять вывод типов для дженериков, подобных этим, это отдельный вопрос. У средств проверки типов, таких как mypy, определенно не возникнет проблем, а IDE, такие как PyCharm, обычно большую часть времени выполняют достаточно хорошо, но вам, возможно, придется провести некоторое тестирование, чтобы увидеть, что поддерживается и что не поддерживается.