Боюсь, не будет особенно чистого способа решения этой проблемы - по крайней мере, такого, о котором я лично не знаю.Как вы заметили, ваши сигнатуры типов содержат фундаментальную неоднозначность, которую mypy не допустит: если вы попытаетесь вызвать Add
с аргументом типа None
, mypy принципиально не сможет определить, какой из указанных вариантов перегрузкисовпадения.
Подробнее об этом см. в документации mypy по проверке инвариантов перегрузки - найдите абзац, обсуждающий «по своей сути небезопасно перекрывающиеся варианты», и начните читать оттуда.
Однако в этом конкретном случае мы можем свободно покачиваться, излагая перегрузки для более точного соответствия фактическому поведению во время выполнения.В частности, у нас есть это замечательное свойство: если один из аргументов равен «None», мы должны также вернуть None.Если мы закодируем это, mypy в конечном итоге будет удовлетворен:
@overload
def Add(this: None, that: None) -> None:
...
@overload
def Add(this: Foo, that: None) -> None:
...
@overload
def Add(this: Bar, that: None) -> None:
...
@overload
def Add(this: Baz, that: None) -> None:
...
@overload
def Add(this: None, that: Foo) -> None:
...
@overload
def Add(this: None, that: Bar) -> None:
...
@overload
def Add(this: Foo, that: Foo) -> Foo:
...
@overload
def Add(this: Bar, that: Bar) -> Bar:
...
@overload
def Add(this: Baz, that: Bar) -> Baz:
...
def Add(this, that):
if this is None or that is None:
return None
else:
return this + that
x: Optional[Baz]
y: Optional[Bar]
reveal_type(Add(x, y)) # Revealed type is 'Union[Baz, None]'
Тот факт, что это работает, может поначалу показаться удивительным - в конце концов, мы передаем аргумент типа Optional[...]
и все же ни одна из перегрузоксодержат этот тип!
То, что делает mypy здесь, неофициально называется "union math" - оно в основном отмечает, что x
и y
оба являются объединениями типа Union[Baz, None]
и Union[Bar, None]
соответственно, ипытается выполнить духовный эквивалент вложенного цикла for, чтобы проверить каждую возможную комбинацию этих союзов.Таким образом, в этом случае он проверяет вариант перегрузки, соответствующий (Baz, Bar)
, (Baz, None)
, (None, Bar)
и (None, None)
, и возвращает возвращаемые значения типов Baz, None, None и None соответственно.
Окончательный тип возврата - это объединение этих значений: Union[Baz, None, None, None]
.Это упрощает до Union[Baz, None]
, который является желаемым типом возврата.
Основной недостаток этого решения, конечно, заключается в том, что оно чрезвычайно многословно - возможно, в невыносимой степени, в зависимости от того, сколькоэти вспомогательные функции, которые у вас есть, и то, как широко распространена эта проблема «мы могли бы вернуть« Нет »», есть в вашей кодовой базе.
Если это так, то вы могли бы объявить «банкротство» в отношении «Нет»'по всей вашей кодовой базе и запустите mypy с отключенным режимом «строгого необязательного» * 1036 *.
Короче говоря, если вы запустите mypy с флагом --no-strict-optional
, вы дадите команду mypy предположить, чтоNone является действительным членом каждого класса.Это то же самое, что и то, как Java предполагает, что 'null' является допустимым членом каждого типа.(Хорошо, каждый не примитивный тип, но все что угодно).
Это ослабляет безопасность типов вашего кода (иногда резко), но позволит вам упростить ваш код, чтобы он выглядел так:
class Foo:
value: int
def __init__(self, value: int) -> None:
self.value = value
# Note: with strict-optional disabled, returning 'Foo' vs
# 'Optional[Foo]' means the same thing
def __add__(self, other: 'Foo') -> Foo:
result = self.value - other.value
if result > 42:
return None
else:
return Foo(result)
@overload
def Add(this: Foo, that: Foo) -> Foo:
...
@overload
def Add(this: Bar, that: Bar) -> Bar:
...
@overload
def Add(this: Baz, that: Bar) -> Baz:
...
def Add(this, that):
if this is None or that is None:
return None
else:
return this + that
x: Optional[Baz]
y: Optional[Bar]
reveal_type(Add(x, y)) # Revealed type is 'Baz'
Строго говоря, проверки на перегрузку должны сообщать об ошибке «небезопасного перекрытия» по той же причине, по которой они возвращались, когда был включен строгий необязательный параметр.Однако, если бы мы это сделали, перегрузки были бы совершенно непригодны, когда строгий опциональный режим отключен: поэтому mypy намеренно ослабляет проверки здесь и игнорирует этот конкретный случай ошибки.
Основным недостатком этого режима является то, что вытеперь вынужден делать больше проверки во время выполнения.Если вы получите какое-то значение типа Baz
, оно может на самом деле быть None
- подобно тому, как любая ссылка на объект в Java может фактически быть null
.
Это может быть хорошим компромиссом вваш случай, так как вы уже разбрасываете эти типы проверок во время выполнения повсюду.
Если вы подпишетесь на школу мысли "ошибка ноль была миллиардной ошибкой" и хотели бы жить в мире строгих опцийодна из техник, которую вы можете использовать, - это постепенное повторение включения строгого опционального в некоторых частях вашей кодовой базы с помощью файла конфигурации mypy .
По сути, вы можете настроить многие (хотя и не все) параметры mypy для каждого модуля через файл конфигурации, что может быть очень удобно, если вы пытаетесь добавить типы в существующую кодовую базу и обнаружите, что переходвсе сразу просто неразрешимо.Начните со слабых глобальных настроек, а затем постепенно делайте их все строже и строже.
Если оба эти параметра кажутся слишком экстремальными (например, вы не хотите добавлять подробную подпись сверху везде, нотакже не хочу отказываться от строгой опциональности), последний вариант, который вы могли бы сделать, это просто silence ошибка, добавив # type: ignore
к каждой строке, mypy сообщает об ошибке "unsafely overlapping types" на.
Это тоже поражение, но, возможно, более локализованное.Даже typhed , хранилище подсказок типов для стандартной библиотеки, содержит несколько разбросанных здесь # type: ignore
комментариев для некоторых функций, которые просто не поддаются выражению с использованием типов PEP 484.
Независимо от того,не это решение, которое вы в порядке, будет зависеть от ваших конкретных обстоятельств.Если вы анализируете свою кодовую базу и думаете, что потенциальная небезопасность - это то, что вы можете игнорировать, возможно, это может быть самый простой путь вперед.