Python проверка mypy типов возвращаемых данных для TypeVar (bound = Union [A, B]) не приводит к ошибкам против TypeVar (A, B) делает ошибку - PullRequest
4 голосов
/ 21 апреля 2020

Я застрял, пытаясь понять границы TypeVar при использовании его двумя различными способами:

  • Enums = TypeVar("Enums", Enum1, Enum2)
  • Enums = TypeVar("Enums", bound=Union[Enum1, Enum2])

Вот код, который я использую:

#!/usr/bin/env python3.6

"""Figuring out why enum is saying incompatible return type."""


from enum import IntEnum, EnumMeta
from typing import TypeVar, Union


class Enum1(IntEnum):

    MEMBER1 = 1
    MEMBER2 = 2


class Enum2(IntEnum):

    MEMBER3 = 3
    MEMBER4 = 4


# Enums = TypeVar("Enums", bound=Union[Enum1, Enum2])  # Case 1... Success
Enums = TypeVar("Enums", Enum1, Enum2)  # Case 2... error: Incompatible return value


def _enum_to_num(val: int, cast_enum: EnumMeta) -> Enums:
    return cast_enum(val)


def get_some_enum(val: int) -> Enum1:
    return _enum_to_num(val, Enum1)


def get_another_enum(val: int) -> Enum2:
    return _enum_to_num(val, Enum2)  # line 35

При работе mypy==0.770:

  • Case 1: Success: no issues found
  • Case 2 : 35: error: Incompatible return value type (got "Enum1", expected "Enum2")

Этот случай очень похож на этот вопрос: Разница между TypeVar ('T', A, B) и TypeVar ('T', bound = Union [A, B])

Ответ объясняет, что при использовании случая 1 (bound=Union[Enum1, Enum2]) допустимо следующее:

  1. Union[Enum1, Enum2]
  2. Enum1
  3. Enum2

И при использовании случая 2 (A, B) допустимо следующее:

  1. Enum1
  2. Enum2

Однако я не думаю, что этот ответ объясняет мою проблему, я не использую Union чехол.

Может кто-нибудь сказать, пожалуйста, что происходит?

Ответы [ 2 ]

2 голосов
/ 24 апреля 2020

Я думаю, что ошибка возникает из-за того, что у средства проверки типов недостаточно информации для вывода возвращаемого типа путем просмотра типов входных аргументов. Хотя обработка может быть улучшена.

Предположим, у вас есть простая функция generi c:

Enums = TypeVar("Enums", Enum1, Enum2)

def add(x: Enums, y: Enums) -> Enums:
    return x

Средство проверки типа может вывести тип возвращаемого значения типом входных аргументов :

add(Enum2.MEMBER3, Enum2.MEMBER4) # ok, return Enum2
add(Enum1.MEMBER1, Enum1.MEMBER2) # ok, return Enum1

add(Enum2.MEMBER3, Enum1.MEMBER2) # not ok

Снова посмотрите на вашу функцию _enum_to_num, у средства проверки типов нет средств для вывода типа возврата, она просто не знает, какой тип будет возвращен, потому что не знает какой тип будет возвращен cast_enum:

def _enum_to_num(val: int, cast_enum: EnumMeta) -> Enums:
    return cast_enum(val)

Идея проверки типа stati c состоит в том, что он оценивает код без выполнения, он исследует типы переменных, не значения c . Посмотрев на тип cast_enum, то есть EnumMeta, средство проверки типов не может определить, вернет ли cast_enum Enums или нет. Похоже, он просто предполагает, что вернет Enum1, и это вызывает ошибку в _enum_to_num(val, Enum2).

Вы знаете, что _enum_to_num(val, Enum2) вернет Enum2, потому что вы знаете значение cast_enum равно Enum2. Значение - это то, что type checker вообще не касается. Это может сбивать с толку, значение переменной cast_enum равно Enum2, в то время как type of cast_enum равно EnumMeta, хотя Enum2 равно type .

Эту проблему можно решить, сообщив средству проверки типов, что типы будут проходить через cast_enum, используя typing.Type:

from typing import TypeVar, Union, Type

...

def _enum_to_num(val: int, cast_enum: Type[Enums]) -> Enums:
    return cast_enum(val)

Ошибка исчезнет, ​​потому что теперь средство проверки типов может определять тип возвращаемого значения.

1 голос
/ 23 апреля 2020

Сначала я немного напишу о том, что mypy видит и сообщает, а затем задаюсь вопросом, является ли это ошибкой mypy.

Сообщение:

Incompatible return value type (got "Enum1", expected "Enum2")

означает здесь, что примерно что Enum2 или подтип этого ожидается. Enum2 является объявленным возвращаемым значением get_another_enum(). Однако mypy считает, что вызов функции _enum_to_num() возвращает тип Enum1.

«Примерно» это связано с тем, что существуют исключения для проверки типов, когда тип не связан или имеет тип Any или Union; но это не относится к этому примеру.

Mypy решает, что функция cast_enum() в _enum_to_num() возвращает первый тип, указанный в Enums - я думаю, что в качестве проверки типа * stati c, она должна выбрать один, и это то, что он делает.

Так что если вы измените порядок в назначении Enums и напишите:

Enums = TypeVar("Enums", Enum2, Enum1)  # Case 2... error: Incompatible return value

Тогда строка 35 будет успешной, но возврат в get_some_enum() завершится ошибкой с сообщением:

error: Incompatible return value type (got "Enum2", expected "Enum1")

Что касается того, является ли это ошибкой mypy, трудно сказать ...

Нет ошибки типа dynamici c, которую можно найти здесь, используя type() или ininstance() функции; работает код работает так же, как и ожидалось.

С другой стороны, Python никогда не проверяет тип возвращаемого значения ни во время компиляции, ни во время выполнения: вы можете изменить тип возвращаемого значения _enum_to_none() на None, и он все равно будет действителен как Что касается интерпретатора Python.

Тогда возникает вопрос: в системе типов stati c, навязанной mypy, это ошибка? (Я не думаю, что PEP 484, 526 или другие номера пытаются решить эту проблему).

Кто-то более квалифицированный должен ответить на вопрос, является ли это ошибкой, которая должна быть обнаружена анализатором stati c, в частности mypy.

См. Ответ Кена Хунга, чтобы узнать, как это сделать. более явным и удалите ошибку mypy.

...