Протоколы в Python. О них мало говорят, а зря

Рассказали, зачем нужны протоколы в Python, почему о них так мало говорят и зачем нужен статический анализатор mypy.

4К открытий9К показов

Когда новички изучают Python, очень много внимания выделено на языковые конструкции и концепции. К примеру, рекурсия или ООП. Но никто не замечает, что речь о типизации в этом языке программирования, если заходит, то сильно вскользь? И, как правило, это фразы в стиле: “Python – это язык с динамической типизацией”. Это понятно, но что это означает?

Итак, это случай типизации в языках программирования, когда тип присваивается переменной во время присваивания ей значения. Такая типизация дает языку гибкость, но при этом допускает возникновение ошибок, связанных с типами переменных, параметров функций и так далее.

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

			def add(a, b):
    return a + b
		

Казалось бы, простейшая функция, возвращающая сумму двух переданных в нее… аргумента. Нет, не числа, в данной ситуации мы так сказать не можем, потому что a и b могут быть чем угодно. Именно поэтому этот код содержит потенциальные ошибки. И именно в этом состоит проблема динамической типизации, которую, как я наблюдаю, так или иначе называют панацеей, избавляющей программиста от лишней “писанины”.

Статический анализатор mypy

Для избавления от этих ошибок нужно как-то явно указать, что a и b могут быть… давайте сначала числами. Это делается с помощью аннотаций типов, о существовании которых я, например, узнал не совсем сразу. Итак, наша функция принимает следующий вид:

			def add(a: int, b: int) -> int:
    return a + b


if __name__ == "__main__":
    print(add(2, 5))
		

На данный момент эти аннотации просто дают знать программисту, какие типы должны иметь аргументы функции и результат какого типа она возвращает.

Для того, чтобы они приняли более “угрожающий” для потенциальных ошибок вид, необходимо использование статического анализатора кода. Если что, это такой инструмент, который анализирует код по его аннотациям типов и выявляет потенциальные ошибки.

Флагманом в этом направлении для Python является mypy. Он устанавливается, как и все пакеты Python, через pip. Запустить его тоже просто:

			python -m mypy <путь до файла или директории>
		

Это самый стандартный способ запуска mypy, и нам его хватит (более подробные настройки можно найти в документации). После запуска мы увидим, что проверка прошла успешно.

Важно отметить, что a и b до сих пор могут быть не целыми числами, но теперь IDE будет информировать нас, что другие типы недопустимы (ввиду того, что, например, PyCharm использует встроенные анализаторы кода), да и проверка mypy тоже не будет проходить.

Таким образом, аннотации в связке с mypy не делают типизацию в Python статической. Они просто дают возможность пользоваться преимуществами статической типизации, но при этом динамическая типизация никуда не девается. По этой причине mypy используют в CI/CD, git hooks и других случаях.

К протоколам

И вот наконец-то мы подобрались к протоколам. Хотя, на самом деле, нужно еще кое-что прояснить. Еще в Python есть такая вещь, как утиная типизация.

Тут тоже все просто: это значит, что, если аргумент функции поддерживает нужный метод, ошибки не возникнет вне зависимости от типа аргумента.

Звучит, наверное, не так просто, поэтому покажу на примере:

			def send_data(sender, data):
    # some actions
    sender.send(data)
    # some actions
		

Да, абстрактным пример вышел, зато он объясняет утиную типизацию и отлично подводит к протоколам. Вне зависимости от типа параметра sender функция отработает без ошибок. Мы можем спокойно создать 2 разных класса со своими атрибутами, конструкторами, другими “магическими” методами, главное, чтобы у них был метод send даже с разной сигнатурой.

Сигнатура функции в Python – это сочетание названий и типов входных параметров и типа возвращаемого результата. Стоит отметить, что, если явно в функции не прописан return, по умолчанию она возвращает None.

Теперь у нас появилась проблема: а как дать понять mypy, что sender должен принимать класс с реализованным в себе методом send? Сейчас на ум приходит использование абстрактных классов, но этот способ допускает использование в функции только производных от него классов, так что это не наш случай.

Выход из такой ситуации – протоколы. Сразу к коду:

			from typing import (
    Any,
    Mapping,
    Protocol,
)


class SenderProtocol(Protocol):
    def send(data: Mapping[str, Any]) -> None:
        ...
		

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

Все, теперь мы можем использовать его как аннотацию типа:

			def send_data(sender: SenderProtocol, data):
    # some actions
    sender.send(data)
    # some actions
		

Все, задача выполнена! Теперь и разработчики, и статический анализатор знают, что из себя представляет из себя параметр sender.

Почему же так мало говорят о протоколах

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

Выводы

Знать о типизации в программировании важно. Эти знания открывают двери в новый мир, который полон интересных вещей, влияющих на понятность кода, а это в наше время ценится в программистах.

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

Надеюсь, что эта статья дала вам то, что вы хотели после ее прочтения. Пишите свою обратную связь в комментариях и ожидайте новых статей. До встречи!

Следите за новыми постами
Следите за новыми постами по любимым темам
4К открытий9К показов