Подсказки с типами могут сильно помочь в работе с большим проектом на Python. Тем не менее иногда они требуют рефакторинга кода. Я писал об этом в прошлом году в этой статье, но тогда я не смог найти хорошего примера, иллюстрирующего то, что я хочу сказать.
Взгляните на этот код:
def process(workflow_step):
func = step[0]
args = step[1:]
return func(*args)
Что можно понять о типах из этого кусочка? То, что workflow_step — это последовательность, первый элемент которой — Callable object, а остальные элементы, если таковые имеются, — аргументы для этого вызываемого объекта. Предположим, что workflow_step — Tuple. Проблема в том, что тип (Tuple) -> Any для этой функции слишком общий и неинформативный. Можем ли мы вызвать её с пустым кортежем? Пройдёт ли (1, 2, 3)?
(Tuple[Callable, Any, ...]) -> Any — намного лучше. Вот только такое определение не поддерживается синтаксисом библиотеки типов Python. В простых случаях одним из возможных выходов из этой ситуации будет использование типов вроде Union[Tuple[Callable], Tuple[Callable, Any]] или чего‑то подобного. В более сложных случаях единственным возможным решением будет рефакторинг.
Есть много вариантов, как это сделать. К примеру, для функции из примера я использовал NamedTuple:
class WorkflowStep(NamedTuple):
callable # type: Callable
args # type: Tuple
def process(workflow_step):
# type: (WorkflowStep) -> Any
return step.callable(*args)
Возможно, это не лучший кусок кода, который вы видели, тем не менее он намного информативнее и структурированнее предыдущего варианта.