Подсказки типов нужны только в функциях?

В чате Telegram-канала задали отличный вопрос — подсказки типов имеет смысл ставить только для аргументов функций и возвращаемых значений или вообще для всех переменных?

И действительно. Как лучше?

В большинстве сценариев подсказок типов достаточно только для аргументов и результатов функций. Если в нашем коде все функции типизированы таким образом, то получается, что IDE и статический анализатор кода понимают тип любой переменной в коде и могут выполнять все проверки.

Объявляя переменную, мы либо задаём её значение в явном виде (и тогда тип переменной равен типу значения):

age = 33
user = User(username="Иннокентий")

Здесь тип переменной age равен типу значения 33, то есть int, а тип переменной user равен User.

Второй вариант создания переменной — присваивание ей значения, которое возвращается функцией:

user = get_user_by_username("Иннокентий")

Если все функции в нашем коде типизированы, в том числе и функция get_user_by_username, то и в таких сценариях тип переменной очевиден. Какой тип данных функция возвращает, такой тип данных у переменной и будет.

Получается, что если все функции, используемые в коде, типизированы, то как правило нет смысла проставлять типы для обычных переменных.

Однако, иногда бывает так, что мы используем внешнюю библиотеку, функции которой нетипизированы. Если функция get_user_by_username в примере выше это функция внешней библиотеки и она нетипизирована, то IDE и статический анализатор кода не знают, какой тип данных вернёт эта функция и потому не знают, какой тип будет у переменной user. Тогда можно подсказать инструментам, явно указав тип:

user: User = get_user_by_username("Иннокентий")

Теперь IDE и статический анализатор будут знать тип переменной и смогут выполнять все проверки. Отлично!

Ещё один сценарий, при котором полезно задать переменной тип — когда мы инициализируем переменную пустым значением, но хотим указать, данные какого конкретно типа там будут.

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

class SomeClass:
    def __init__(self):
        self._some_dict: dict[str, int] = {}
    
    def some_method(self):
        self._some_dict["some_key"] = 123  # Всё ок по типам
        self._some_dict[123] = "some_key"  # Ошибка типов!