У меня есть собственный класс ведения журнала, который, в частности, обрабатывает регистрацию предупреждений и ошибок, возникающих во время выполнения. Я также хотел бы иметь возможность регистрировать ошибки утверждения, не будучи слишком навязчивым, определив специальную функцию утверждения, которая выполняет утверждение и регистрирует сообщение об ошибке, если оно не удалось.
Казалось бы, это легко сделать, но средства проверки статических типов, такие как PyLance, не считают функцию встроенной assert
. Рассмотрим этот минимальный пример:
def my_assert(condition: bool, msg: str):
if not condition:
logger.error(msg)
raise AssertionError(msg)
Тогда я ожидал бы, что смогу использовать этот метод как встроенный assert
, не раздражаясь тем, что «Оператор '+' не поддерживается для типа 'Нет'».
def add_one(a: int | None) -> int:
my_assert(a is not None, "a is None")
return a + 1 # Pylance lints this as being possibly None
Есть ли способ указать средствам проверки типов, что это ложное срабатывание и его следует рассматривать как утверждение? В противном случае, есть ли лучший способ сделать это, сохранив при этом упрощенную запись?
По сути, вы ищете однострочник (т. е. простой оператор), который выполняет сужение типа для типа.
Python допускает только определяемые пользователем обратные вызовы, сужающие тип обратные вызовы формата
Callable[[argument to narrow type of, <any other arguments> ...], <narrowed type>]
и сужение типов может происходить только в контексте вызова с помощью assert
или if...elif...else
-проверок. Итак, вам придется вписать в этот формат свою собственную функцию log-then-assert. На not None
s (Pyright Playground) будет работать что-то вроде следующего:
import typing as t
from logging import getLogger
T = t.TypeVar("T")
logger = getLogger()
def check_type(arg: T | None, condition: bool, msg: str) -> t.TypeGuard[T]:
if not condition:
logger.error(msg)
raise AssertionError(msg)
return True
def add_one(a: int | None) -> int:
assert check_type(a, a is not None, "a is None")
reveal_type(a) # int
return a + 1
Я считаю, что в более общих случаях вам придется связать нежелательные типы с классом. Примерно так (Pyright Playground):
Other = t.TypeVar("Other")
class not_(t.Generic[Other]):
@classmethod
def check_type(cls, arg: T | Other, condition: bool, msg: str) -> t.TypeGuard[T]:
if not condition:
logger.error(msg)
raise AssertionError(msg)
return True
def add_one(a: int | str) -> int:
assert not_[str].check_type(a, type(a) is not str, "a is str")
reveal_type(a) # int
return a + 1
Спасибо за ответ, это действительно должно решить мою проблему, поскольку я считаю, что средства проверки типов в любом случае не обрабатывают утверждения, не связанные с типом.
Что, если вы выполнили вход в систему
try...except AssertionError
верхнего уровня, а не во вспомогательную функцию нижнего уровня? Тогда внутренний код может использовать обычный старыйassert
со всеми предоставляемыми удобствами.