TypeGuard в Python: проверка неизвестного значения перед обработкой

После загрузки настроек программа получает неизвестное значение. Обработчику нужен список строк, но простого isinstance(value, list) мало: внутри списка могут оказаться числа. Проверку формы хочется выполнить один раз и сделать её результат понятным анализатору типов.

Тип должен следовать из реальной проверки

TypeGuard сообщает анализатору, какой тип доказан при результате True. В примере функция возвращает TypeGuard[list[str]], поэтому обязана проверить и контейнер, и каждый элемент. Одна аннотация ничего не проверяет автоматически. Если тело вернёт True для списка чисел, анализатор поверит неверному утверждению.

После успешного условия функция join может работать со строками без дополнительного cast. При неуспехе вызывающий код получает ValueError с описанием ожидаемой структуры. Это удобная граница между внешними значениями и внутренней обработкой.

Отдельно решить смысл пустого списка

all на пустой последовательности возвращает True. Для списка строк это обычно корректно: в нём нет элемента неправильного типа. Если предметная область требует хотя бы одно имя, добавьте отдельную проверку непустоты. Не смешивайте структурное условие «каждый элемент — строка» с бизнес-условием «выбран хотя бы один адресат».

Пример принимает пустой список и возвращает пустую строку. Смешанный список и строка вместо списка отклоняются. Такой набор показывает обе части контракта и не ограничивается одним удобным входом.

from typing import TypeGuard

def is_string_list(value: object) -> TypeGuard[list[str]]:
    return isinstance(value, list) and all(isinstance(x, str) for x in value)

def labels(value: object) -> str:
    if not is_string_list(value):
        raise ValueError("expected list of strings")
    return ", ".join(value)

assert labels(["north", "south"]) == "north, south"
assert labels([]) == ""
for value in [["north", 3], "north", None]:
    try:
        labels(value)
    except ValueError:
        pass
    else:
        raise AssertionError(value)
print("input narrowed after validation")

Учитывать изменение после проверки

Проверка описывает объект в момент вызова. Если другой код затем изменит тот же список, гарантия может перестать выполняться. Для устойчивого снимка создайте новый кортеж строк после проверки или договоритесь об исключительном владении. TypeGuard не блокирует мутации и не добавляет синхронизацию.

Используйте этот приём для повторяемых проверок формы, где обычного isinstance недостаточно. Для одной локальной проверки иногда понятнее сразу построить проверенное значение. В обоих случаях доказательство типа должно находиться рядом с реальной проверкой элементов.

Соединить проверку входа и типизацию

Пройдите публичную практику type hints и mypy. Затем добавьте функцию проверки внешнего значения и посмотрите, как анализатор понимает успешную ветку. Откройте программу для дальнейшей работы с типами и тестами.

Продолжить на практике

  • Python для продвинутых: ООП, типизация и тестирование ↗

    Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.