Типизация Python с обработкой исключений

Следующий код хранится в файле с именем sample.py.

import re
from typing import Optional, Tuple
 
def func(path: str) -> Optional[Tuple[str, str]]:
    regex = re.compile(r"/'([^/']+?)'/'([^/']+?)'")
    try:
        return regex.match(path).groups()
    except AttributeError:
        return None

Линтер Mypy Python выдает следующую ошибку при анализе кода:

sample.py:8: error: Incompatible return value type (got "Union[Sequence[str], Any]", expected "Optional[Tuple[str, str]]")
sample.py:8: error: Item "None" of "Optional[Match[str]]" has no attribute "groups"

Хотя regex.match(path).groups() может возвращать тип None, который не имеет атрибута groups, результирующее исключение обрабатывается, и обработка указывается в возвращаемом типе. Однако Mypy, похоже, не понимает, что обрабатывается исключение. Насколько я понимаю, Optional[Tuple[str, str]] является правильным типом возвращаемого значения, а Mypy вместо этого настаивает на том, что менее конкретный тип Union[Sequence[str], Any] является правильным. Как правильно использовать обработку исключений при вводе Python? (Обратите внимание, что я не прошу альтернативных способов написания кода без использования обработки исключений. Я просто пытаюсь предоставить минимальный и полный пример, в котором средства проверки типов Python ведут себя не так, как я ожидал, при обработке исключений.)


person user7147804    schedule 19.07.2019    source источник


Ответы (1)


Mypy на самом деле не понимает исключения на глубоком уровне — в данном случае он не понимает, что, поскольку вы перехватываете AttributeError, он может игнорировать «что, если regex.match(path) равно None?» кейс.

В более общем плане фундаментальное предположение, которое делает mypy, состоит в том, что когда у вас есть некоторый объект foo с типом Union[A, B] и вы делаете foo.bar(), оба типа A и B имеют метод bar().

Если только один из этих типов имеет метод bar(), вам нужно будет сделать одну из нескольких вещей:

  1. Дайте mypy достаточно информации, чтобы сузить объединение только до одного из соответствующих типов, прежде чем выполнять доступ к атрибуту. Например, isinstance проверяет, x is not None проверяет...
  2. Признайте, что вы пытаетесь сделать что-то, что средство проверки типов не понимает, и соглашайтесь на подавление сгенерированной ошибки. Например, вы можете преобразовать тип, добавить комментарий # type: ignore, найти способ сделать foo динамическим типом Any...
  3. Найдите способ изменить код, чтобы полностью обойти эту проблему.

(В этом конкретном случае я полагаю, что другой альтернативой может быть отправка запроса на вытягивание в mypy, добавляющего поддержку этого шаблона. Но я не уверен, что это действительно осуществимо: изменение любого фундаментального предположения — сложная работа в нескольких измерениях. )

Точно так же Mypy также не понимает регулярные выражения на глубоком уровне - например. не пытается анализировать ваше регулярное выражение, чтобы определить, сколько групп вы получите, и поэтому не поймет, что ваше конкретное регулярное выражение соответствует строкам ровно с двумя группами. Лучшее, что он может сделать, — это утверждать, что группа вернет неизвестное количество строк — отсюда и тип Sequence[str] вместо Tuple[str, str].

На самом деле такого рода ограничения довольно распространены в программах проверки типов: большинство систем типов в основных языках на самом деле не поддерживают способ предиката возвращаемого типа на основе содержимого любых переданных фактических значений. Такие системы типов (зависимый тип системы, системы уточняющего типа...) довольно сложны в реализации и часто имеют крутую кривую обучения для конечных пользователей.

Однако было бы проще заставить mypy поддерживать это на основе максимальных усилий, написав плагин mypy, если вы согласны. В частности, попробуйте взглянуть на get_method_hook() и get_function_hook().

person Michael0x2a    schedule 20.07.2019