Могу ли я использовать информацию о наборе текста из другой функции в качестве возвращаемого типа в Python?

Как правильно аннотировать возвращаемый тип функции sum_two в следующем примере?

from typing import Any, TypeVar

T = TypeVar('T')
S = TypeVar('S')

def sum_two(first: T, second: S):
    return first + second

Предполагая, что оператор __add__ правильно аннотирован для всех возможных аргументов, которые будут переданы этой функции, есть ли способ выразить возвращаемый тип как возвращаемый тип вызова __add__ для объектов типа T и S?

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


person Jonathan    schedule 30.01.2020    source источник
comment
Использование typing.get_type_hints(func)['return'] должно работать. На самом деле, вы можете использовать его, Python отлично его запускает и добавляет правильную аннотацию, в чем можно убедиться, посмотрев на аннотацию вашей функции sum_two (на самом деле это не так, поскольку сама __add__ не имеет аннотации типа). Но, к сожалению, mypy жалуется на «Неверный тип комментария или аннотации».   -  person Konrad Rudolph    schedule 30.01.2020


Ответы (1)


Теоретически вы можете выполнить его часть, сделав first общим протоколом, который позволит вам "захватить" возвращаемый тип __add__. Например:

# If you are using Python 3.7 or earlier, you'll need to pip-install
# the typing_extensions module and import Protocol from there.
from typing import TypeVar, Protocol, Generic

TOther = TypeVar('TOther', contravariant=True)
TSum = TypeVar('TSum', covariant=True)

class SupportsAdd(Protocol, Generic[TOther, TSum]):
    def __add__(self, other: TOther) -> TSum: ...

Затем вы можете сделать следующее:

S = TypeVar('S')
R = TypeVar('R')

# Due to how we defined the protocol, R will correspond to the
# return type of `__add__`.
def sum_two(first: SupportsAdd[S, R], second: S) -> R:
    return first + second

# Type checks
reveal_type(sum_two("foo", "bar"))  # Revealed type is str
reveal_type(sum_two(1, 2))          # Revealed type is int
reveal_type(sum_two(1.0, 2))        # Revealed type is float

# Does not type check, since float's __radd__ is ignored
sum_two(1, 2.0)

class Custom:
    def __add__(self, x: int) -> int:
        return x

# Type checks
reveal_type(sum_two(Custom(), 3))  # Revealed type is int

# Does not type check
reveal_type(sum_two(Custom(), "bad"))

Однако этот подход имеет несколько ограничений:

  1. Он не обрабатывает случаи, когда нет совпадения __add__ в «первом», но есть соответствие __radd__ в «втором».
  2. Вы можете получить странные результаты, если измените Custom так, чтобы __add__ было перегрузкой. Я думаю, что по крайней мере в mypy в настоящее время есть ошибка, из-за которой он не знает, как правильно обрабатывать сложные случаи, связанные с подтипами и перегрузками.
person Michael0x2a    schedule 01.02.2020