Python и Go отличаются по свойствам, и поэтому могут дополнять друг друга.

Существует распространённое заблуждение, будто простой и лёгкий — это одно и то же. В конце концов, если некий инструмент легко использовать, то и его внутреннее устройство должно быть просто понять, разве не так? И обратное тоже верно, да? На самом деле, всё как раз наоборот. В то время, как по духу оба понятия указывают на одно и то же (итог со стороны кажется лёгким), на практике такая поверхностная лёгкость достигается огромной подкапотной сложностью.

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

Здесь и начинаются нюансы — оказывается, та лёгкость, с которой на Python удаётся выразить что угодно, обходится отнюдь не даром. Под капотом Python лежит тяжеленный интерпретатор, и даже для выполнения единственной строки кода в нём должно произойти множество операций. Если вам доводилось слышать, что Python — «медленный» язык, то знайте, что эта явственная «медлительность» связана с рядом решений, которые интерпретатору приходится принимать во время выполнения. Но, как мне кажется, это даже не основная проблема. Сложность всей среды выполнения Python и её экосистемы, наряду с некоторыми произвольно принятыми решениями по поводу управления пакетами в этом языке — вот причины крайней хрупкости этой среды. Из‑за хрупкости зачастую возникают случаи несовместимости и отказы во время выполнения. Вполне обычны ситуации, в которых ты на какое‑то время отходишь от работы с приложением на Python, затем возвращаешься к нему через несколько месяцев — и обнаруживаешь: экосистема изменилась настолько, что твоё старое приложение уже даже не запустить.

Разумеется, это грубое, даже чрезмерное упрощение: сегодня даже дети знают, что такие проблемы решаются при помощи контейнеров. Действительно, благодаря Docker и другим подобным инструментам можно навсегда «зафиксировать» зависимости в базе кода Python так, что эта база кода будет работать практически вечно. Но, фактически, это просто перекладывание ответственности и сброс всей сложности на уровень инфраструктуры ОС. Не конец света, но нельзя смотреть на эту проблему сквозь пальцы и недооценивать её.

От лёгкости к простоте

Если бы мы взялись решать проблемы, существующие в Python, то получили бы на выходе нечто вроде Rust — язык крайне производительный, но печально известный своим высоким порогом вхождения. На мой взгляд, Rust совсем не лёгок в использовании и, более того, он совсем не прост. В то время, как сегодня Rust на самой волне хайпа, я со всем моим опытом (программирую лет 20, первые шаги делал на C и C++) не могу взглянуть на сниппет кода Rust и с уверенностью прочитать его с листа.

Лет пять назад я открыл для себя Go, как раз, когда работал с системой, основанной на Python. Притом, что с синтаксисом Go я освоился не с первого захода, мне сразу же стало ясно, насколько просты идеи, лежащие в его основе. Язык Go сделан именно так, чтобы его было просто понять любому человеку в организации — как джуну, который только что со студенческой скамьи, так и старшему менеджеру по инженерии, который мельком взглянул на код. Скажу больше, при всей простоте языка Go синтаксис его обновляется очень редко. Последним крупным изменением были дженерики, добавленные в версии 1.18, и то после серьёзного обсуждения, продлившегося целое десятилетие. В большинстве случаев, если взглянуть на код Go, написанный хоть пять дней, хоть пять лет назад, он будет выглядеть очень узнаваемо и должен просто работать.

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

temperatures = [
    {"city": "City1", "temp": 19},
    {"city": "City2", "temp": 22},
    {"city": "City3", "temp": 21},
]

filtered_temps = {
    entry["city"]: entry["temp"] for entry in temperatures if entry["temp"] > 20
}

Чтобы написать такой же код на Go, требуется значительно больше постучать по клавиатуре, но в идеале в нём будет на один уровень абстракций меньше, чем в Python, который зависит от своего подкапотного интерпретатора:

type CityTemperature struct {
    City      string
    Temp float64
}

// ...

temperatures := []CityTemperature{
    {"City1", 19},
    {"City2", 22},
    {"City3", 21},
}

filteredTemps := make(map[string]float64)
for _, ct := range temperatures {
    if ct.Temp > 20 {
        filteredTemps[ct.City] = ct.Temp
    }
}

Притом, что аналогичный код можно написать и на Python, в программировании действует негласное правило: если в языке предоставляется более лёгкий (читай, более лаконичный, более элегантный) вариант, то программисты будут склоняться именно к нему. Но «лёгкость» — понятие субъективное, а «простота» должна быть простой для кого угодно. Когда одно и то же действие можно совершить несколькими способами, это приводит к развитию разных стилей программирования, причём зачастую множество стилей может сочетаться в одной и той же базе кода.

Да, Go можно назвать многословным и «скучным», но при работе с ним запросто ставится ещё один плюсик: компилятору Go приходится выполнять совсем немного работы, при сборке исполняемого файла. Компиляция и запуск приложений на Go зачастую не уступает и даже выигрывает в скорости у Python (там ведь ещё нужно завести интерпретатор) или у Java (там нужно запустить виртуальную машину). Неудивительно, что самый быстрый исполняемый файл — это нативный исполняемый файл. Он не так быстр, как аналоги на C/C++ или Rust, зато в несколько раз проще на уровне исходного кода. Этим небольшим «недостатком» Go я готов пренебречь. И на закуску: бинарники Go связываются статически. Это значит, что их можно собрать где угодно, а запускать на той машине, на которой нужно. Не будет никаких зависимостей, связанных со средой выполнения или библиотеками. Ради удобства мы всё равно обёртываем в контейнеры Docker наши приложения, написанные на Go. Тем не менее, эти приложения получаются миниатюрными и потребляют лишь малую толику памяти и процессорного времени по сравнению с аналогичными приложениями, написанными на Python или Java.

Как сочетать Python и Go себе на пользу

Наиболее прагматичное решение, к которому мы пришли в нашей работе — комбинировать лучшие черты лёгкости Python и простоты Go. Мы считаем, что Python — отличный полигон для прототипирования. Именно здесь рождаются идеи, принимаются или отвергаются научные гипотезы. Python просто создан для исследования данных и для машинного обучения, а поскольку нам по работе приходится постоянно иметь дело с этими областями, едва ли имеет смысл заново изобретать велосипед на каких‑нибудь других языках. Кроме того, Python лежит в основе фреймворка Django, девиз которого — стремительная разработка приложений (вряд ли ему в этом найдутся равные, но для полноты картины, конечно, упомяну Ruby on Rails и Phoenix для Elixir).

Допустим, в проекте нужно обойтись минимальным управлением со стороны пользователя, а администрирование данных требуется организовать внутри приложения (у нас таких проектов большинство). В данном случае мы делаем скелетное приложение на Django, так как в нём есть встроенный Admin, фантастически полезная вещь. Как только грубо сработанная на Django пробная модель начинает напоминать продукт, мы смотрим, насколько серьёзная часть этой модели поддаётся переписыванию на Go. Поскольку в приложении на Django уже определена структура базы данных и понятно, как выглядят модели данных, довольно легко написать на Go код, заступающий на смену Django. Через несколько итераций достигаем симбиоза, где обе половинки мирно сосуществуют поверх одной и той же базы данных, а коммуникация между ними осуществляется через самую простую систему сообщений. В конечном итоге «оболочка» Django превращается в оркестратор — то есть отвечает за администрирование и запускает те задачи, которые затем поступают на обработку в область приложения, написанную на Go. Та часть, что написана на Go, отвечает за всё прочее, от клиентских API и конечных точек до бизнес‑логики и обработки заданий, возникающих на машинном интерфейсе.

Такой симбиоз нас пока не подводил и, надеюсь, далее тоже не подведёт. Как‑нибудь напишу пост, в котором более подробно разберу обрисованную здесь архитектуру.

Спасибо за внимание!

Комментарии (42)


  1. mentin
    07.12.2023 20:34

    Отличная статья, спасибо за хороший перевод.

    Но задумался, можно ли назвать простым язык в котором есть сборка мусора?


    1. lorc
      07.12.2023 20:34
      +3

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


      1. SadOcean
        07.12.2023 20:34
        +10

        си тоже довольно прост.
        Правила языка можно описать на нескольких страницах.
        Есть, как водится, нюанс


        1. mentin
          07.12.2023 20:34

          статья же как раз про это - С прост, но не легок


      1. Kreastr
        07.12.2023 20:34

        Просто на ассемблере приходится часто еще и работать напрямую с железом а не только ядрами процессора. И граница размытая. Вот например модуль прямого доступа к памяти (DMA) это технически не часть ядра процессора и можно жить не используя ее, но разница в производительности огромная, когда знаешь и используешь. Если учитывать всю подобную периферию, которая обычно скрыта за ОС (которая сама тоже является сложной сущностью) и драйверами, то ассемблер вообще не простой.


        1. lorc
          07.12.2023 20:34

          Ну я исходил из предположения что мы делаем не baremetal прошивку, а приложение под ОС. Соответственно, все железо абстрагировано ядром и тут нет разницы - что python что ассемблер.


    1. includedlibrary
      07.12.2023 20:34
      +1

      scheme достаточно прост, сборка мусора в наличии


      1. mentin
        07.12.2023 20:34

        ну так себе прост, народ жалуется что рантайм не маленький. Поискал минимальный рантайм, нашел «When we tried to find a Lisp/Scheme implementation that we could run on an SPE for the Cell processor, we didn’t find anything that could actually fit. All Scheme implementations we found, including the supposedly small ones, needed more than 500 kByte in total (when statically linked, which is necessary for the SPEs).» - примерно размер Go runtime.


    1. calculator212
      07.12.2023 20:34

      Зависит от контекста. "Простой" для изучения и понимания, если опустить ряд особенностей то в целом да, "простой" с точки зрения устройства вряд ли. Ну и сейчас мне кажется почти любой язык высокого уровня не назвать простым.


      1. mentin
        07.12.2023 20:34

        Я в контексте использования слов статьёй: про изучение и понимание лёгкий, а простой про устройство внутри.


  1. nameisBegemot
    07.12.2023 20:34
    +3

    Это где образуется "сложность всей среды выполнения Python"? Единственное, где это было отчётливо и массово - переход со второй версии на третью. В остальном Питон более чем адаптируется под проект и экосистему. Проблем с несовместимостью версий зависимостей нет от слова совсем.


    1. Zara6502
      07.12.2023 20:34
      -2

      я в году 19 предпринял последнюю попытку использования питона и было отчетливое деление на 2.7 и 3 версии, что в итоге обязывало выбирать между библиотеками, так как многие интересные библиотеки были только в какой-то одной версии. Эта проблема исчезла? Победил 3?

      ну и для меня, как начинающего сразу откисала половина гитхаба и SO, так как куча всего было только под 2.7


      1. Alpensin
        07.12.2023 20:34
        +3

        В 19 году эксклюзивно под второй питон было только что-то старое. На третий питон перешли значительно раньше.


        1. Zara6502
          07.12.2023 20:34

          Ну мне как новичку неведомо старое оно или новое. У меня были задачи которые я хотел решать, я вместо C# решил делать это на питоне. Половину нашел под 2.7, вторую под 3.0, потыкался, помыкался, и вернулся на C#. Ну и в целом если сравнивать с Nuget установка бибилиотек для питона проходила весьма негладко, что-то не работало в принципе.

          На третий питон перешли значительно раньше

          Кто? Я до сих пор натыкаюсь на проекты под 2.7. Если вы имеете в виду какие-то крупные бизнес-библиотеки - ну может быть, но мир на них не заканчивается.


      1. nameisBegemot
        07.12.2023 20:34

        Ну тут нужно индивидуально смотреть. Сдаётся мне, что большинство библиотек, работающих только по 2 версию питона могли перестать обновляться. И застряли нс уровне многолетней давности.

        Но это предложение. Просто я (для себя конечно) не могу назвать библиотек или инструментов, не поддерживающих Питон 3


        1. Zara6502
          07.12.2023 20:34

          Просто я (для себя конечно) не могу назвать библиотек или инструментов, не поддерживающих Питон 3

          У меня использование питона чисто утилитарное. Нашел проект, скопировал, запустил. Если в требованиях какие-то библиотеки, то ставлю и их. И вот это всё очень и очень фрагментировано и разрозненно. Для C# это в основном 4.8 и >6, а тут или 2.7 или 3. Но для питона регулярно что-то да не работает и требует внимания, модификации и переписки, а когда это не основной для тебя язык, то желания глубоко погружаться в это - нет. Я обычно закидываю код на питоне в сервис, который переписывает его на C#, а потом исправляю если потребуется.

          Ну и проектов требующих 2.7 в своей работе (коммерческих проектов) примерно 50% из того что мы ставим. Возможно есть версии свежее на 3, но не покупать же новое только потому что они таки перешли на 3 питон.


          1. nameisBegemot
            07.12.2023 20:34

            У вас прям очень индивидуальная ситуация. Тем более если доверять скайнету переписывать код с питона на шарп.

            Согласен, где-то есть древнее легаси. Но в целом в Питоне нет тех проблем с зависимостей, как например, в джаве. Где нужен целый копановщик (или как это у них называется), который автоматизирует весь это зоопарк.


      1. petrovich09
        07.12.2023 20:34

        в 19-ом году прошлого века


        1. nameisBegemot
          07.12.2023 20:34

          Удивительно, но Питон на лет 5 моложе с++. Хотя плюсы считаются древним артефактом


  1. titan_pc
    07.12.2023 20:34
    +1

    У интерпретации есть огромный плюс. Подтягивать новый код во время выполнения. То есть Ваше приложение работает, пишет код и подтягивает его себе. И такое ни одному бинарнику не снилось. Но почему то все в python используют его слабые стороны вместо сильных. Python фактически может кодить себя сам не отходя от кассы(без перезапуска процесса). На этом можно строить воистину удивительные вещи. Но все делают web да ml.

    Ну и python - не легкий, когда капаешь глубже "Возьми либу А + Б и проект готов".

    Также как и Go не простой, когда пришла пора устраивать разгон. И ты такой пошёл и понял что в простой мапе можно ошибиться 40 раз.


    1. zabanen2
      07.12.2023 20:34
      -1

      Но все делают web да ml

      бобук сказал, что популярность python заслужена в большой степени в том числе быстротой numpy
      ruby в моем видении сильно похож на python, в 2015 я не знал что выбрать - оба интерпретируемых ит.п., а сейчас где ruby без ror, где python в ml без numpy


    1. playermet
      07.12.2023 20:34
      +6

      Бинарник может:

      • подтянуть другой бинарник своей платформы и запустить

      • подтянуть скомпилированную заранее в свою платформу функцию и слинковать

      • вызвать установленный в систему компилятор и передать сгенерированный файл

      • вызвать встроенный в систему язык программирования (например, PowerShell)

      • вызвать установленный в систему язык программирования (например, Python)

      • иметь запас настраиваемости логики с достаточной для задачи свободой

      • иметь кастомную виртуальную машину и при необходимости DSL к ней

      • иметь полноценный встроенный компилятор (например, TinyCC)

      • иметь встроенный интерпретируемый язык (например, Lua)

      • JIT-ить функции соответствующими фреймворками (например, AsmJit)

      • JIT-ить функции по хардкору из инструкций платформы вручную


    1. orenty7
      07.12.2023 20:34

      такое ни одному бинарнику не снилось

      https://www.man7.org/linux/man-pages/man3/dlopen.3.html


  1. MAXH0
    07.12.2023 20:34

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

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

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


  1. Zara6502
    07.12.2023 20:34

    причём академический бэкграунд здесь обычно вообще не требуется. Для работы с Python редко приходится прибегать к теории типов или понимать, где, что и как именно хранится в памяти, в каких потоках выполняется тот или иной фрагмент кода, т.д. Более того, через Python можно влиться в работу с некоторыми наиболее обширными библиотеками

    еще с 80-х я с полпинка начинал пользоваться ассемблером, паскалем, си, си++, делфи, php, perl, c#, но абсолютно не могу пользоваться питоном, я бы сказал - мы с ним не на одной волне.

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


    1. Hlorabi
      07.12.2023 20:34

      Я бы не сказал, что "интуитивное" программирование это хорошо.


      1. Zara6502
        07.12.2023 20:34

        Ну для меня этого хорошо, когда нет борьбы с фантазией разработчика ЯП. Когда пишу на C# создается ощущение что они мне в мозг залезли и сделал всё так как я бы хотел. Разные плюшки всё добавляют и добавляют - прелесть.


    1. AcckiyGerman
      07.12.2023 20:34

      Ну раз вы начинали с си++ и делфи, то естественно с# вам кажется интуитивным, там те же базовые концепции - типы, ООП и скобочки {}
      А людям, которые начинали, например с BASIC, или математикам, питон кажется интуитивнее - пишешь a = 2 , а какой там тип под капотом для решаемой задачи в большинстве случаев не важно.


      1. Zara6502
        07.12.2023 20:34

        почему вы решили что я начинал с си++ и делфи, если я явно указал что "еще с 80-х"???

        я начинал с BASIC, потом CLIPPER/FOXPRO, ASM, TURBO PASCAL, TURBO C, DELPHI, C++, PERL, PHP, C# - это в хронологическом порядке.

        питон кажется интуитивнее - пишешь a = 2

        Кому как.

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

        Это часто создает проблемы и мешает отладке.

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

        Безусловно есть те что до сих пор на Турбо Паскале пишет или фортране


  1. NeoCode
    07.12.2023 20:34
    +4

    Питон странный. С одной стороны параноидальная строгость при приведении типов (все только явно), с другой - можно случайно объявить новую переменную, просто опечатавшись в имени существующей.

    А вот Go очень приятный (для меня как С/С++ программиста).


    1. economist75
      07.12.2023 20:34
      +1

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

      Кстати, у датасатанистов полно однобуквенных названий переменных. И это нормально, потому что ошибка сразу всплывет (кусочки кода интерактивно выполняются в ячейках JupyterLab). И потом, вероятность опечатки обратно пропорциональна длине переменной, и для однобуквенных и _ она ничтожна. Зато короткие имена переменных становятся сленгом и повышают понимание.

      Выбор комфортнее, чем его отсутствие. За время типизации я напишу и сам код (без типов), примерно в 50% случаев (сфера - DS). Для меня это сигнал что Python по прежнему оптимален для DS, быдлокодинга, MVP и вообще для обучения, обкатки идей. То что он медленный - все в курсе, и ругать его за это - моветон. Придумают новый (Mojo) - перейдем. Ускорят - останемся. Допилят Julia - подумаем. Скажут Go - нет, пока плюсы не перевешивают.


      1. Akuma
        07.12.2023 20:34
        +1

        Ну хз хз. На днях так и не смог заставить VSCode подсвечивать и подсказывать tensorflow на питоне.

        Причем я понимаю что если посидеть подольше, то наверно что-то придумаю, но ни с Rust, ни с Go подобных проблем нет в принципе.


        1. voidMan
          07.12.2023 20:34

          PyCharm вам в помощь


        1. Dancho67
          07.12.2023 20:34

          А вы venv активировали? Без него автоподсказок вы от вс кода не дождетесь. Это все равно, что пытаться в го писать без инициализированного модуля в го или крейта в расте.


          1. Akuma
            07.12.2023 20:34

            Нет. В доках про него ничего не было :)

            Как-нибудь попробую. Но в целом все равно Питоновская среда показалась менее дружелюбной для старта.


  1. n43jl
    07.12.2023 20:34
    +2

    На эту тему есть классика: очень хорошее выступление 2011 года Rich Hickey "Simple made easy", где он рассуждает о Simple != Easy.


  1. Kelbon
    07.12.2023 20:34
    +1

    Я бы сказал Python простой, Go примитивный


    1. kekoz
      07.12.2023 20:34
      +1

      Просто Python сделан программистом для себя (точно так же, как программистами для себя был сделан Unix), а Go создан корпорацией с тысячами “рабов на галерах”, которой было необходимо иметь такие вёсла, чтобы даже самый зелёный джун мог с приемлемой эффективностью грести, но при этом не допускать совсем уж откровенных косяков.


  1. TomTom07
    07.12.2023 20:34
    +1

    Какое для меня имеет значение насколько сложен питон под капотом если это не доставляет мне проблем?

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

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

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

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


    1. Areso
      07.12.2023 20:34

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

      Не все компилируемые языки такие. Небольшой пет компилится на Го быстре, чем запускается рантайм на Питоне =)


  1. D7ILeucoH
    07.12.2023 20:34
    +1

    Статья интересная, но на самом деле кажется что тут сравнение неуместное - оба языка пересекаются разве что в бэкенде (поправьте если не прав, но по моему Go программистов только на бэкенд ищут).

    Будет интересно посмотреть сравнение Python и доработанного Kotlin 2.0, который должен бросить вызов первому на его же поприще - в ML и ноутбуках. Может быть тогда Kotlin завезут в ЕГЭ, как по мне Kotlin на порядок удобнее буквально для всего, пусть и немного сложнее. И да, Kotlin для бэкенда тоже используют, на замену Java.


    1. Dancho67
      07.12.2023 20:34

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