TypeVar аннотирует аргументы против возвратов - PullRequest
0 голосов
/ 27 марта 2020

Рассматривая случай, когда есть родительский класс и несколько потомков, унаследованных от этого родителя. Установите TypeVar для типа, указывающего на ребенка, когда ребенок передается или возвращается. Для простоты и наглядности был создан только один дочерний элемент.

from typing import TypeVar


class Parent(object):
    pass

class Child(Parent):
    pass

T_co = TypeVar("T_co", bound=Parent)

mypy будет реагировать по-разному в зависимости от того, где применяется тип подсказки. Когда применяется возврат, mypy вызовет следующую ошибку

def hint_return() -> T_co:
    return Child()
Incompatible return value type (got "Child", expected "T_co")mypy(error)

Но mypy не применимо к аргументу.

def hint_arg(child: T_co):
    pass

register_arg(Child())

Почему это появляется несоответствие? И как правильно использовать TypeVar для подсказок типа для возвратов?

1 Ответ

2 голосов
/ 27 марта 2020

Существуют в основном два неявных правила для того, как вы должны использовать TypeVars:

  1. TypeVar должен использоваться хотя бы один раз в подсказках типов аргументов.
  2. TypeVar должен появляться в по крайней мере дважды в сигнатуре функции.

Если вы не будете следовать этим двум правилам, вы либо в конечном итоге создадите сигнатуру типа функции, которая на самом деле мало что делает и содержит лишние TypeVar или невозможна сделать вывод типа.

Ваша функция hint_return является примером функции, которая нарушает правило 1. Причина, по которой это проблематично c, заключается в том, что когда mypy видит вызов, подобный:

x = hint_return()

... он пытается определить, какой тип x использует , просто информацию, доступную на сайте вызовов и в сигнатуре типа - он не проверяет тело hint_return.

(Но что, если mypy попытается использовать существующий тип x в качестве подсказки? Что ж, нет никакого способа, которым hint_return мог бы на самом деле воспользоваться этой информацией во время выполнения, так что информация, следовательно, не может быть релевантной для целей вывода типа. Это опять-таки является отражением правила 1: TypeVar должен быть заменен более конкретным типом c при вызове функции, что означает, что вам нужно на самом деле указать, что именно этот конкретный c тип равен в качестве ввода.)

Ваша функция hint_arg является примером функции, которая нарушает правило 2. В этом случае ваш TypeVar заканчивается бесполезным: было бы проще просто переписать вашу функцию следующим образом:

def hint_arg_simplified(child: Parent):
    pass

В конце концов, замена T_co на фактический тип, который был передан, не имеет смысла. Поскольку hint_arg все еще должен быть способен принимать любой произвольный подтип Parent, то, как вы реализуете hint_arg и hint_arg_simplified, должно быть точно таким же, несмотря ни на что.

(Помните, что если функция набрана для принятия Parent, она должна фактически принимать Parent и любой подтип Parent. То есть mypy предполагает, что ваши типы следуют принципу подстановки Лискова и соответственно выполняют проверку типов)

Но выполняя:

T = TypeVar('T', bound=Parent)

def two_args_v1(x: T, y: T) -> None: pass

... сильно отличается от выполнения:

def two_args_v2(x: Parent, y: Parent) -> None: pass

В первом мы знаем, что x и y должны быть точно такого же типа, в то время как мы не знали, что для последний. Это актуальная и новая информация, которую можно использовать при выводе типа.


Одно замечание о классах generi c. На первый взгляд может показаться, что они нарушают эти правила. Например, mypy совершенно доволен следующим определением класса, хотя кажется, что оно нарушает оба правила! Почему?

from typing import Generic, TypeVar

T = TypeVar('T')

class Wrapper(Generic[T]):
    # Violates rule 2?
    def __init__(self, x: T) -> None:
        self.x = x

    # Violates rule 1 and 2?
    def unwrap(self) -> T:
        return self.x

Ну, это потому, что мы на самом деле не смотрим на полную сигнатуру типа. Обычно мы опускаем тип из self, но на самом деле они все еще там. Как только мы добавим обратно в автоматически выводимый тип self, становится ясно, что оба правила фактически соблюдаются:

class Wrapper(Generic[T]):
    def __init__(self: Wrapper[T], x: T) -> None:
        self.x = x

    def unwrap(self: Wrapper[T]) -> T:
        return self.x
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...