Как стать автором
Обновить
76.74
Райффайзен Банк
Развеиваем мифы об IT в банках

Python — серьезный язык для разработки backend

Время на прочтение13 мин
Количество просмотров73K

Всем привет! Меня зовут Денис Аникин, я тимлид в команде Chat в Райффайзенбанке. А также представитель внутреннего Python-сообщества, так называемый «community lead» (об этом как-нибудь в другой раз). В этой статье я хотел поговорить про отношение к Python среди разработчиков и обсудить все основные претензии, которые очень давно следуют за языком по пятам.

В начале я хочу коротко рассказать про причины этой статьи — она не претендует на статус серьезного исследования, статья, скорее, немного ироничная. И возникла она как ответ на бесконечные камни, летящие в сторону моего любимого языка.

Я часто принимал участие в больших дискуссиях, действительно ли Python можно назвать серьезным языком программирования. Именно это, во многом, и стало причиной для написания этой статьи. Здесь я хочу попытаться донести идею, что как минимум для меня Python — действительно серьезный, без всяких скидок, язык программирования. Заодно я хочу воодушевить тех, кто уже пишет на Python, но начал немного стесняться этого, начитавшись интернета.

Также я вспоминаю нашу внутреннюю шутку, которая сформулирована моим коллегой, Даниилом: «Денис начинает свой понедельник с очередного доклада на тему, почему Python — серьезный язык». Я долго думал над этой шуткой (извините, я не гений), а потом решил: почему бы и не начать с этого статью? :)

Поэтому я хочу поговорить про проблемы Python и обсудить накопившиеся к нему основные претензии.

Динамическая типизация

За динамическую типизацию Python ругали практически все — это одна из крупнейших претензий к нему в среде разработчиков. Но на самом ли деле динамическая типизация — такое уж зло? Как минимум, разработка на языках динамической типизации проще, быстрее и зачастую приятнее — конечно, это субъективный тезис, но он имеет право на существование.

Какой-нибудь JSON-парсер на языке со статической типизацией будет гораздо более многословным и тяжелым в написании, чем на языке с динамической типизацией (ох уж эта элегантная связка orjson и pydantic). Где-то, где мы в Python отобьемся манипуляциями в рантайме, придется подтаскивать большие объёмы кодогенерации.

Аннотации — благо
Аннотации — благо

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

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

Да, у нас есть gradual типизация. MyPy, typing, аннотации типов — это позволяет нам значительно улучшить качество кода, уберечься от массы ошибок, получить экстрафункциональность языка. Например, в Python нет констант, но с помощью связки typing и MyPy можно получить их аналог. Аннотации в Python можно анализировать как статически, так и динамически: в typing есть интроспекция, появляются проекты вроде Beartype

На базе аннотаций типов построены современные фреймворки, такие как Pydantic (строгие схемы валидации в рантайме) или FastAPI. Я считаю вот так: Python в связке с аннотациями типов являет нам gradual типизацию и тем самым предоставляет здоровый компромисс между преимуществами динамической и статической типизаций. Он не порождает хтоническое чудовище, каким его часто объявляют в интернете. Напротив, нам дают способ писать и быстро и надежно, здесь и сейчас.

Где-то тут к нам подкрадывается вопрос о типобезопасности. Полагаю, что аннотации типов проверку на типобезопасность не пройдут, хотя вопрос это довольно сложный, не для моего уровня понимания. На текущий момент ошибок типов с MyPy и аннотациями типов я не встречал, но уверен, что где-то при определенном стечении обстоятельств это возможно. Единственное, что видно у нас — увеличение надежности нашего кода (пока мы пишем наши бэкенды, аннотации часто помогают отлавливать ошибки), умение гибридно использовать аннотации и почти никаких type error в коде.

Также стоит вспомнить, что люди, предпочитающие статическую типизацию, часто говорят, что для маленьких проектов динамическая типизация «не страшна», а на действительно больших мы начинаем чувствовать её недостатки.

Честно говоря, пункт уже раздулся, поэтому я просто набросаю свои мысли списком:

  • Статический анализ хорош везде;

  • Как мы уже сказали выше, в Python есть статистический анализ, пускай и немного уступающий «настоящему» выводу типов — «настоящей» типобезопасности. Мы используем его в полный рост, и с ним можно писать действительно большие проекты

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

Скорость

Этот аспект языка часто подвергается критике очень многими разработчиками. И если динамическая типизация — сложная и неоднозначная тема для обсуждения, то скорость — то, о чем говорят вообще все. 

Если посмотреть на синтетические бенчмарки, то Python часто можно обнаружить в самому низу турнирных таблиц. Многие разработчики считают это Ахиллесовой пятой Python, что не может не сказываться на восприятии языка

Когда обсуждают скорость Python, обычно вспоминают про C++, ведь на нем все бенчмарки выглядят в сто раз лучше, чем на Python. Но если мы начнём разбираться в вопросе, то станет ясно, что скорость Python на данном этапе — не такая уж проблема. Конечно, Python действительно медленный в CPU-bound задачах. И тут у вас в голове возникает мысль — если он такой медленный, то это точно проблема!

На мой взгляд, дело обстоит так. В современной web-разработке полно i/o-bound нагрузки: часто мы принимаем запросы по сети, отправляем их в сеть, читаем из БД, пишем в БД, оперируем с файлами и так далее.

Одни из действующих «лиц» CPU-bound и IO-bound задач
Одни из действующих «лиц» CPU-bound и IO-bound задач

Для этого типа задач у Python есть прекрасный ответ — асинхронный подсет языка, нативный async/await, event loop из коробки и uvloop, для тех, кто хочет ещё быстрее. С помощью этой части Python мы можем эффективно утилизировать ресурсы CPU. А для исключительно CPU-bound в мире Python тоже много «припарок»: multiprocessing, subprocess, Pypy, Cython, Numba и так далее. Поэтому асинхронный Python работает очень даже быстро.

Подведу субъективный итог: многое, если не всё, можно смело писать на Python, а CPU-bound, при необходимости (если стандартная библиотека не тянет), переписывать на более быстрых языках и ставить эти микросервисы-воркеры за очередями сообщений. Разве это не идеальное сочетание?

Язык для новичков

Довольно часто Python считают языком для новичков. Ему учат на каждом шагу, в мире миллион курсов по Python. Топовые блоггеры обещают обучить ему чуть ли не за один вечер, говорят, что все сразу же получают серьезную зарплату, уютный офис и счастливое будущее. На этом слишком радужном фоне за Python прочно закрепился имидж языка для новичков, где достаточно написать пару for и def, объявить несколько переменных — и вот ты уже профессиональный Python-разработчик уровня middle. А потом «перерастаешь» и идешь писать на «серьезном языке».

Это все какой-то интернет-морок, потому что Python, как и любой другой язык, требует освоения, изучения, времени.

В самом языке у нас целая куча всяких знаний, нюансов, сложностей и декларативных частей. Судите сами:

  • У нас есть протокол итерации. Конечно, можно сказать, что итератор — не совсем часть функционального программирования, что итераторы есть и в других языках. Но я хочу подчеркнуть, что итераторы — это не совсем просто, особенно вначале, особенно когда нам нужно реализовывать свои.

  • Декораторы. Здесь тебе и функции высшего порядка, замыкания, три уровня вложенности для параметризированных декораторов — слишком много всего для такого «простого» языка. Я сам по началу долгое время не понимал паттерн «декоратор», с трудом писал их. И я знаю, что у многих с этим есть проблемы, это видно как минимум на собеседованиях.

  • Метаклассы. Классы, создающие классы? Функция type, она же класс? А тип type — тоже type? Все классы создаются type? Популярный сценарий применения — ORM? Можно, пожалуйста, мне другой «простой» язык?

  • Библиотека functools в полном составе :)

  • Генераторы. Можно спрашивать на собеседованиях: «А для чего вы используете генераторы?» — и услышать очень много разных ответов. Некоторые вообще не понимают, зачем они нужны. А ведь генератор поддерживает протокол итерации, в него можно слать значения, yield from — ну, сами все понимаете.

  • Контроль зависимостей. Venv, virtualenv, pipenv, poetry, pip tools, pdm, pipx, pyproject и так далее.

  • Инфраструктура вокруг Python. Крайне полезно для работы было бы знать, что такое pypa, pypi, psf — а это ещё отдельный пласт знаний.

  • PEP. Иногда спрашиваю людей: «А что такое PEP?», — и самый частый ответ: «Стандарт кода». Даже опытные разработчики помнят PEP8 в лучшем случае.

  • Около сотни магических методов. Хорошо было бы в них просто ориентироваться.

  • Могущественная интроспекция и «мета-язычные» вещи. Runpy, importlib, trace, traceback, gc, inspect, sys, typing.get_type_hints, typing.get_origin, typing.get_args.

  • Встроенные классы ошибок, методов и типов. Можете выполнить и получите 156: import builtins; len(dir(builtins)). Это количество встроенных классов ошибок, методов, типов. Желательно помнить многое из этого.

  • Новые вещи. Оператор «морж», pattern matching.

  • Конкурентное выполнение. Threading с кучей нюансов, multiprocessing, subprocess, свежий communicating sequential processes «паттерн» (PEP 554).

  • GIL. Комментарии не нужны, но они будут (я не умею лаконично, помогите).

  • Особые языковые возможности. Dict, list, set comprehensions, generator expressions, например.

  • Асинхронность. Это отдельный «подсет» языка, где уже целая своя вселенная с кучей пакетов и даже отдельными (не встроенными в язык) концепциями, вроде structured concurrency.

  • Аннотации типов. Или аннотированный Python — тоже, считай, свой мини-«подсет» языка, вводящий новый понятийный аппарат, и новый вид типизации — структурную саб-типизацию, ближайшим аналогом которой из мира динамической типизации можно было бы назвать утиную. 

  • Динамическая типизация. Строгая (местами «обходимая» полимформизмом, или неявным приведением int к float), утиная типизация, typing.Protocol (про него выше), gradual типизация. Скажите мне — это правда так просто?

Типичный диалог (а иногда и монолог) в интернете
Типичный диалог (а иногда и монолог) в интернете

И я бы мог ещё продолжать какое-то время. Этот список я составил не для того, чтобы отпугнуть от языка, но мне хочется продемонстрировать, что Python — не такой простой язык программирования. Действительно, на нём легко начать, у него низкий порог первоначального входа. Но это вовсе не язык только лишь для новичков, такое отношение к нему порождает безответственный подход к разработке. Язык взял много хорошего от других, он не простой, со своими плюсами и минусами, очень красивыми местами и не самыми лучшими. И всё это требует освоения.

GIL 

Можем сказать, что мы снова про скорость. Когда заходит разговор о тредах и Python, о скорости и Python, сразу на сцену выползает наш любимый и обожаемый GIL.

Ограничения GIL известны всем — в Python при попытке распаралеллить любую CPU-bound нагрузку с помощью тредов, разработчики неизбежно сталкиваются с тем, что в один момент у всегда будет работать только один поток. Тема абсолютно выдающаяся и имеющая за собой такой поток реминисценций, что про это даже немного неловко говорить, — но поговорить нам, всё-таки, в рамках статьи нужно.

Издалека проблема GIL — и сложная, и, как кажется, практически нерешаемая (мы все помним, что на этот счет сказали Гвидо, Дэвид Бизли; UPD: тут недавно вышла статья, говорят о nogil… но давайте не будем об этом). 

В современных бэкендах и Python накал страстей вокруг GIL сильно стих — веб-сервисы очень часто по своей природе могут быть асинхронными, а для этого у нас есть множество чудесных фреймворков, вроде FastAPI, поэтому ограничения GIL нас не так сильно касаются. При этом горизонтально мы масштабируемся процессами, как в синхронных бэкендах, так и в асинхронных. У многих есть кубер — и там мы масштабируемся тоже процессами, только над ними стоит абстракция уровнем выше — pod.

Изрядно поношенный GIL
Изрядно поношенный GIL

Некоторые разработчики спрашивают, зачем нам тогда треды? 

В современном Python их используют для одной интересной штуки — на них можно делать wannabe-асинхронный код, при этом не делая асинхронный код, как ни странно. То есть можно написать на тредах что-то, что занимается интенсивной i/o-нагрузкой. И тогда внезапно все станет асинхронным за счет того, что GIL отпускается на i/o-операциях. 

Это важное примечание, так как это сложный и неочевидный нюанс работы GIL — он описан в документации, но кто же её читает — шутка :). Но благодаря ему мы можем получить преимущество даже в синхронном коде, а также не блокировать петлю событий, когда у нас возникает потребность вызвать синхронное i/o в петле событий (вспомним про запись файлов, привет экзекьюторам).

Python — «неказистый язык для набрасывания прототипов на Django»

Частое мнение в интернете — Python годен только для быстрого, реактивного набрасывания прототипов на Django, а все остальное удел «серьезных языков» (тм). И это ещe одно, с моей точки зрения, не очень корректное суждение. 

Конечно, на Django, на Python быстро набрасывают прототипы — я не буду отрицать очевидного. Но на языке делают не только это, описанное — лишь малая часть возможностей экосистемы Python. Однако быстро прототипирующие люди, использующие язык одним способом, почему-то взяли на себя прерогативу оценивать позиционирование языка по своей сфере. И с этим я не согласен. 

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

Pytest — это прекрасный фреймворк для написания тестов и яркий представитель этого тулсета. Он позволяет выстраивать очень сложные тестовые сценарии с инверсией зависимостей всего с помощью двух инструментов — fixtures и parametrize. С их помощью можно делать очень сложные тесты — подробно изучать производительность и бизнес-логику системы.

В тестировании, помимо Pytest, у нас есть Hypothesis — отличный фреймворк для fuzzy, property тестирования. Фреймворк настолько впечатляет, что недавно, на Python Language Summit 2021, стало известно, что его используют core developer'ы и с помощью него нашли ошибки в PEG парсере самого Python.

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

from hypothesis import given
from hypothesis.strategies import text


def encode(input_string: str) -> list:
    """Это просто синтетический пример."""
    count: int = 1
    prev: str = ""
    lst: list = []
    character: str
    for character in input_string:
        if character != prev:
            if prev:
                lst.append((prev, count))
            count = 1
            prev = character
        else:
            count += 1
    lst.append((character, count))
    return lst


def decode(lst: list) -> str:
    """Это просто синтетический пример (2)."""
    q: str = ""
    character: str
    count: int
    for character, count in lst:
        q += character * count
    return q


@given(text())
def test_decode_inverts_encode(s):
    """И здесь мы получаем совершенно невероятный объем тестов. При том, 
    что повесили 1 декоратор."""
    assert decode(encode(s)) == s

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

Вообще, в Python есть огромное количество вспомогательных библиотек для тестирования — Faker, Factory boy, Mixer, Seed, куча расширений для Pytest — например, для параллелизации. Поэтому говорить, что Python можно использовать только для быстрого прототипирования на Django — немного некорректно.

Безопасность

Как-то раз нам написали комментарий, что за Java и C# стоят крупные компании, бизнес, а за Python только Гвидо и никакой ответственности нет. Видимо, это означало, что языком пользоваться страшно и от этого он абсолютно несерьезен.

Мне кажется, этот вопрос довольно сложный. Крупные проблемы языковой среды — не такой уж и частый сценарий. Но вряд ли при серьезных проблемах разработчики на Java или C# моментально получат их решение.

А ещё можно вспомнить, что современный софт пишется с использованием огромного количества Open Source библиотек, которые пишутся сторонними разработчиками, лежат в открытом доступе — и баги там случаются часто, а гарантий их исправления нет и быть не может. Тогда как серьезные проблемы Python, в случае их возникновения, точно будут исправлены. К тому же у Python «сжался» релизный график.

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

Популярность

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

У нас есть аналитика GitHub, здесь Python сразу за JavaScript
У нас есть аналитика GitHub, здесь Python сразу за JavaScript
Google-тренды довольно наглядно показывают, что Python очень долго рос все эти годы и умудрился своей неспешной змеиной «походкой» обогнать мощных конкурентов
Google-тренды довольно наглядно показывают, что Python очень долго рос все эти годы и умудрился своей неспешной змеиной «походкой» обогнать мощных конкурентов
Рейтинг PYPL (некоторые говорят, что он не очень объективен)
Рейтинг PYPL (некоторые говорят, что он не очень объективен)
Любимый многими RedMonk. Кто-то считает его довольно объективным
Любимый многими RedMonk. Кто-то считает его довольно объективным
Менее известный рейтинг IEEE Spectrum, который агрегирует данные из 11 источников, включая (но не только) некоторые из описанных выше и ниже
Менее известный рейтинг IEEE Spectrum, который агрегирует данные из 11 источников, включая (но не только) некоторые из описанных выше и ниже
Stackoverflow говорит, что по популярности Python сейчас третий
Stackoverflow говорит, что по популярности Python сейчас третий
Вот и всем известный и бесконечно критикуемый TIOBE
Вот и всем известный и бесконечно критикуемый TIOBE

Причин роста много, но кроме известных, для себя я предпочитаю выделять такую: одна из сильнейших черт Python в том, что он по пути вбирал в себя огромное количество разных парадигм и подходов, зачастую беря из них если не лучшие, то как минимум хорошие куски. Именно поэтому он умудряется быть таким универсальным и при этом довольно дружелюбным. Хм, дружелюбный сосед-питон? (шутка, предоставлена ЗАО «бумер-кринж»).

И о минусах

Что? Секундочку, ведь я только что активно поддерживал Python, а теперь начинаю перечислять минусы? Дело в том, что мне не хочется выглядеть как фанатик в розовых очках. Поэтому я просто назову уже не совсем типовые вещи, но тоже часто встречающиеся:

  • GIL всё ещё нас беспокоит. В итоге I/O-bound с небольшим количеством CPU-bound может привести к тому, что некоторые сигналы будут не доставлены.

  • У нас нет оптимизации хвостовой рекурсии (а нужна ли она?). Это большой топик.

  • Некоторые не любят пробелы… вспомним Whython. Я до сих пор не понимаю, в чем проблема — я везде код форматирую пробелами, а в Python они дают возможность избежать написания «лишних» скобок. Но не признать того, что есть недовольные, нельзя.

  • Lambda — есть разработчики, которые хотели бы видеть их как функции. Тогда как в Python они устроены в качестве выражений.

  • Python 2 и 3. Это неактуальный топик на текущий день, и Python 3 принес столько невероятных вещей, что о второй версии говорить нет желания. Но стоит признать — это было больно, и многие ещё помнят, насколько болезненно дался переход. Некоторых это отвращает от языка: подсознательно ты ждешь Python 3 vs 4.

  • Специфический подход к ООП.

  • Смешение парадигм даётся не всегда просто.

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

Небольшие итоги

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

Python используется большинством авторитетных компаний, таких как Google и Яндекс. При этом популярность языка продолжает расти, а так же Гвидо недавно на Python Language Summit 2021 обещал, что с поддержкой Microsoft он сделает Python быстрее раз в пять, во многом с помощью jit. Может быть из конца списка языков в бенчмарках мы переместимся в середину, и уж там то точно заживем.

В Python фантастические веб-фреймворки. Можем вспомнить Django или FastAPI — это выдающиеся фреймворки, со своими плюсами и минусами. Современный Python можно брать для написания быстрых, хороших и серьезных бэкендов. В Python-среде много крутых разработчиков и приятных людей!

Python жутко популярен и продолжает расти. Потенциал его роста не исчерпан.

Для меня Python — язык для написания бэкенда номер один. Язык, с которого я начинаю любой бекенд в 2021 и только в каких-то исключительных ситуациях беру другие.

А ещё на Python очень приятно писать код — и этого совершенно не стоит стесняться.


Эта статья — расширенная версия доклада с IT-конференции Райффайзенбанка <code/R>. Здесь много новых подробностей, но если хотите услышать часть голосом — смотрите видео.

Теги:
Хабы:
Всего голосов 46: ↑37 и ↓9+35
Комментарии99

Публикации

Информация

Сайт
www.raiffeisen.ru
Дата регистрации
Дата основания
1996
Численность
5 001–10 000 человек
Местоположение
Россия