Как стать автором
Обновить

Я люблю питон, и вот почему он меня бесит

Уровень сложностиПростой
Время на прочтение18 мин
Количество просмотров59K
Всего голосов 212: ↑202 и ↓10+228
Комментарии145

Комментарии 145

Это был интересный, хорошо оформленный пост! Держи плюсик в карму)

Я люблю питон, и вот почему он меня бесит

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

А меня бесит, что нельзя понять стандартным образом сколько я прождал GIL. Да и вообще, профайлинг GIL отстойный. Да, ничего не делающие аннотации типов - тоже такое себе.

Прям интересно услышать мнения минусующих данный опус. От чего у вас подгорает?

Аннотации типов позволяют использовать статический анализатор. А вот включить его или нет остаётся за пользователем, как и все в питоне - полная свобода.

В общем, питон — динамический. Очень динамический. Даже слишком. По моему опыту, в 99% случаев мне эта динамика вообще не впёрлась

Так может вы просто выбрали для своих задач не тот язык программирования? :)

НЛО прилетело и опубликовало эту надпись здесь

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


Я на нём пишу всякие скрипты для генерации всего подряд: wrapper'ов, конфигураций, упаковки пакетов и т.д. и т.п. Тоже заметно быстрее и удобнее чем использовать для этого те же C#/Java на которых у нас собственно пишется весь "продуктивный" код.

Оно удобно, конечно, но можно динамики будет немного меньше? :)

А нет других языков где её "немного меньше"? Ну то есть зачем её из питона убирать если куча людей выбирает питон именно из-за неё?

А нахрена вы "прототипы" пишете на Java, когда много-много лет есть отличные котлин и груви? Причем берете и ставите типы — и получаете меньше динамики. Что-то мешает? Я понимаю когда мешает отсутсвие под рукой JVM, но у вас же есть?

Вы не правильно поняли. На Java я их как раз таки не пишу. Их я пишу на Питоне.

А на Java у нас всё ещё есть ряд легаси-проектов которые не особо имеет смысл целиком переписывать заново на данный момент.

Ну хорошо, на Java не пишете, я неправильно сформулировал вопрос. Я тоже не предлагаю вам легаси приложение на груви или котлин переписывать.


Тоже заметно быстрее и удобнее чем использовать для этого те же C#/Java

Речь вот об этом. Вам питон удобнее Java. Для скриптов — верю. Так вот груви или котлин — удобнее питона. Их недостаток — что нужно JVM. Это не всем нравится. Но у вас же это есть.


Почему вы прототипы не пишете на условном груви, при наличии под рукой JVM? Вот просто пример — я пытался скажем на питоне писать некий скрипт для мониторинга приложения. Если приложение на Java — то его метрики зачастую уже лежат в JMX. А добраться до JMX из скрипта на груви в сто раз проще, чем из скрипта на питоне. И таких примеров я мог бы вспомнить десятки. Это не значит, что груви всегда выигрывает у питона для таких проектов — но очень часто таки да (при том что синтаксис у груви и котлина, как по мне, местами сильно приятнее питоновского).

Потому что мне так проще. Вот как-то так получается :)

Плюс ещё пожалуй потому что наши инженеры или эмбеддеры в питон тоже умеют, а в джаву/котлин/груви нет.

Ну "я так не умею" — это тема, несомненно. Мы учим :)


У нас девопс весь на дженкинсе, там так или иначе либо "а ля груви", либо прямо чистый груви. Так что многие умеют.

Вот только вопрос зачем их именно этому учить? Чтобы лично мне было удобнее? Так себе аргумент. Особенно учитывая что у них то JVM не стоит.

И наши инженеры это не девопс. Это именно что инженеры: электротехника, мехатроника, машиностроение. Хорошо что хоть на чём-то умеют :)

Чтобы лично мне было удобнее?

Ну как вы догадываетесь, я вряд ли смогу объективно измерить, что мне дает груви вместо питона. Субъективно — как правило объем кода сокращается (не на порядок, а скажем в пару раз), а сопровождение упрощается. Функциональность при этом либо не страдает, либо больше. Качество и функциональность доступных библиотек — как правило в моем случае сильно выше. Скажем, не так просто будет найти аналог условного Apache POI, если вдруг придется поработать с файлами Office. Ну это тема неисчерпаемая, не буду углубляться.


что у них то JVM не стоит.

Ну, у нас вот Java применяется, и я с трудом могу представить себе аргумент такого вида "у нас тут на хосте оно не стоит".

Ну, у нас вот Java применяется, и я с трудом могу представить себе аргумент такого вида "у нас тут на хосте оно не стоит".

Ну так Java она у нас применяется. Не у них :)

груви

Шикарная тема для цикла статей на тему "почему оно меня бесит".

Ну, меня он не бесит. Могу на противоположную тему накатать описание практического опыта.

НЛО прилетело и опубликовало эту надпись здесь

Кстати, а чем современная java многословна, если появился синтаксический сахар типа var/lombok/streams?

НЛО прилетело и опубликовало эту надпись здесь

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

из плюсов только не нужна IDE и создание проекта (но в C# внезапно тоже есть консольный компилятор и писать можно в блокноте, но по понятным причинам я так никогда не делаю.

C# требует слишком много телодвижений. Питон можно просто в консоли быстро набросать и запустить.

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

а работа в самой консоли для меня совершенно неудобна, мне проще запустить VS и там же набрать код для питона и запустить - и синтаксис подсвечивается и дебажить проще. а сразу с нуля что-то без ошибок написать это уровня Hello World? Тем более в структуре кода постоянно то туда то сюда бегаешь пока пишешь, как это вменяемо делать в консоли?

Можно увидеть простой пример чем питон будет быстрее и удобнее того же Шарпа?

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

да пожалуйста:

Вы это пробовали у себя настроить и запустить? Я пробовал.


да хоть и просто вызовом dotnet run

Можно ссылочку на туториал как при помощи dotnet run можно выполнять C# код прямо в консоли? Ну то есть чтобы я набрал в консоли условное Console.WriteLine("Hello world!");, нажал Enter и оно выполнилось?

что там пробовать, вот только что на маке поставил одной командой
dotnet tool install -g dotnet-script
дальше запускаешь dotnet script или dotnet-script как больше нравится и пишешь Console.WriteLine("hello world")
если оставляешь в конце строки ; то repl не исполняет выражение и ждет следующего, если же ; нет получаешь вывод результата

через dotnet run нельзя выполнить код в консоли, но можно скомпилировать и запустить уже написанное приложение без всякой IDE

что там пробовать, вот только что на маке поставил одной командой

А теперь тот же номер но со сторонними библиотеками.


через dotnet run нельзя выполнить код в консоли

А на питоне можно. И иногда это очень удобно.

да легко, > #r "nuget: AutoMapper, 6.1.0"
using AutoMapper;
Console.WriteLine(typeof(Mapper));

dotnet run не для repl, смысл ее сравнивать, нужен repl используй dotnet script, он все умеет что нужно

Есть такой нюанс: для многих научных работников до сих пор матлаб (куда менее динамичный) весьма люб.

Это я сейчас даже не про себя: очень многие в ближайшем окружении его любят и не хотят от него никуда уходить…

Мб это связано с удобной IDE, мб с понятными типами переменных…

Самое смешное в том, что эти мои знакомые ещё и на питоне что-то пытаются писать. Но, как любые правильные математики/физики, пишут на нём любые свои задумки, как на фортране. И это не от великого знания фортрана (его они как раз почти и не знают). Просто для многих научных работников все эти генераторы и прочее — пустой звук. Они просто как-то используют библиотеки, которые кто-то за них написал. Лучше бы реально на фортране писали…

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

Нет ЯП без недостатков, питонисты их не скрывают вообще. Надо отдать должное - все статьи подобного рода (даже иностранные) - пропитаны здоровой самоиронией, читать приятно. Стоит ли исправлять - да. Но именно неспешно, как было сделано с unicode в Python 2->3. Тогда было жестко, но язык никто не бросил.

Ясность синтаксиса позволяет прощать Питону любые несуразности

По-моему в статье как раз идет речь о том, что синтаксис и приемы не совсем ясные

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

Ясность синтаксиса - это про среднюю длину ключевого слова, объем кода в символах и строках, число скобок, спецсимволов итд. Код на Python лаконичен и читается с двух метров. Да, это заслуга значащих отступов и удобного Py-синтаксиса без ";" и т.н. закрывающих (циклы, условия, функции, классы итд) команд.

Я совершенно не вижу отличий в ясности кода между Питоном и, например, Паскалем.

Единственное - отступы - это сразу же и форматирование. А форматирование - это залог удобности чтения. Т.е. если в Паскале форматирование - доп. опция, в Питоне - необходимость изначально.

Питон

number = 1
 
while number < 5:
    print(f"number = {number}")
    number += 1
print("Работа программы завершена")

Паскаль (речь о современной версии языка - Делфи)

var number := 1;

while number < 5 do 
begin
  writeln('number = ', number);
  Inc(number);
end;
writeln('Работа программы завершена');

Питон

message = "Hello"
 
for c in message:
    print(c)

for i in range(1, 10, 2):
    print(i)

Паскаль

var message := 'Hello';

for var c in message do
  writeln(c)

for var i in range(1, 10, 2) do
    print(i)

Питон

def say_hello():    # определение функции say_hello
    print("Hello")


say_hello()         # вызов функции say_hello
say_hello()
say_hello()

Паскаль

procedure say_hello(); // определение функции say_hello
begin
  writeln('Hello');
end;


say_hello();           // вызов функции say_hello
say_hello();
say_hello();

Питон

def get_message():
    return "Hello"

message = get_message()  # получаем результат функции get_message в переменную message
print(message)           # Hello 
 
# можно напрямую передать результат функции get_message
print(get_message())     # Hello 

Паскаль

function get_message: string;
begin
  Exit('Hello');
end;

var message := get_message();  // получаем результат функции get_message в переменную message
writeln(message);              // Hello 

// можно напрямую передать результат функции get_message
writeln(get_message);          // Hello 

Питон

def multiply(n):
    def inner(m): return n * m

    return inner


fn = multiply(5)
print(fn(5))        # 25
print(fn(6))        # 30
print(fn(7))        # 35

Паскаль

function multiply(n: integer): TFunc<integer, integer>;
begin
  Result := function(m: integer): integer begin Exit(n * m); end;
end;


var fn := multiply(5);
writeln(fn(5));        //25
writeln(fn(6));        //30
writeln(fn(7));        //35

Отличие - типы, которые требуется указать, а не просто аннотации. Точки с запятой, которые читаемость не ухудшают. Операторные скобки - тоже не ухудшают. В Питоне тоже, если будет множество вложенных блоков появляется проблема определения в каком блоке мы находимся и без средств разработки это бОльшая проблема, чем при множестве вложенных begin/end;

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

Без пробелов и комментов код на Питон в ~2 раза меньше занимает в байтах (из ваших же примеров). Значит он вдвое быстрее набирается и исправляется, содержит вдвое меньше опечаток.

Есть несколько общепризнанных критериев существенности отличий, p=0.05, 10%, 20%. Различия на +100% - точно существенны. Думаю что многие пользователи видят эти различия и выбирают то, что им важнее. Метрики популярности тому свидетели.

Такие архитектурные изыски сабжа как кавычки 3-х разных типов, отступы, срезы, списковые включения, итераторы, генераторы - оказались очень удачными, кмк. Я почти уверен что все это мелькало в других ЯП раньше, но собрать все подобные трюки в одном языке - это было смелое решение, и "ставка сыграла".

С длинной вы не совсем правы.
1. Чем длиннее ключевое слово, тем сложнее сделать опечатку. Либо эта опечатка приведет лишь к синтаксической и самой простой ошибке во время компиляции или анализа кода, а не исполнения.
2. Редакторы кода сами помогают написать ключевое слово. В том числе и операторные скобки.
3. Время, которое тратится на написание кода увеличивается минимально из-за длинных ключевых слов.
4. Возьмём, например, ключевое слово var, которое позволяет объявить переменную. Это ключевое слово оберегает ещё и от того, что переменная может быть использована из другой области видимости (про глобальные переменные в Питоне я знаю, и это только подтверждает мои слова, наличием слова global)

Добавьте парочку примеров.

Открыть файл прочитать строку, закрыть, в случае исключения закрыть.

with open(path) as f:
   print(f.read())

Создать список четных чисел от одного до 20

print([n for n in range(21) if n %2 == 0])


Что у Паскаля с литералами для коллекций (списки, словари, множества)?

Как обстоят дела с распаковкой?

for k, v in {1:1, 2:2}.items():
    print(k, v)

Речь не о возможностях языка, а о читаемости. И ваши примеры - это отличный аргумент в пользу нечитабельности (не ясности) Питона.

Читаем первую строку файла. Закроется сам

Writeln(TFile.ReadAllLines(path)[0]);

Создать список четных чисел от одного до 20

    for var i in range(1, 21, 1,
      function(n: Integer): Boolean
      begin
        Result := n mod 2 = 0;
      end)
    do
      WriteLn(i);

Генератор range, он же енумератор с функцией для проверки числа. Точно такое же range как в питоне, но ещё с возможностью указать "фильтрацию". Не генерирует массив, а вычисляет по мере цикла. Я уверен, что вы даже так всё тут легко прочитали, не смотря на то, что конструкция очевидно больше, чем в питоне. Были б ещё лямбды, было бы ещё короче. Но они будут потом.

for k, v in {1:1, 2:2}.items():
    print(k, v)

Этот код лишен смысла, но если мы имеем дело со каким-то созданным словарем, а не с создаваемым на ходу, то

for var Item in Dic do
  writeln(Item.Key, Item.Value);

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

  1. Просто мимо. На каждую манипуляцию с файлом нужно искать или писать утилитарный метод? Неужели, в паскале нет менеджера контекста? Или какого-нибудь оператора, который закрывал ресурс при выходе из блока. Видел недавно язык(zig, кажется), где такой оператор назывался defer.

  2. Создание списка(хотя у вас почему-то числа просто выводятся на экран) занимает на порядок больше строк чем в питоне, что уже является большим минусом, так как ухудшает читаемость кода. Анонимная функция, кстати, тоже читабельности не способствует. Даже в жабе, которая славится своей многословностью, завезли Stream API с лямбдами и если чего-то подобного нет в паскале, то это явно минус.

  3. Опять же, распаковка выглядит удобнее нежели постоянное обращение к Item. Такая же болячка есть у жабы, а вот C# удалось её побороть, добавив возможность деконструкции у KeyValuePair.

Все, о чем вы говорите - лишь синтаксический сахар.

Можно десятки способов реализовать для чтения файла. Вплоть до освобождения при выходе из блока. Это не проблема и не сложность.

Range - всего лишь функция, которая создаёт генератор. Добавить в него функцию ToArray дело 20 секунд.

Опять же, это просто сахар.

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

Не надо забывать, что Паскаль - язык с ручным управлением памяти. И в нем реализован механизм подсчёта ссылок, которого достаточно.

Все, о чем вы говорите - лишь синтаксический сахар.

И этот синтаксический сахар кратно уменьшает сложность кода.

Можно десятки способов реализовать для чтения файла. Вплоть до освобождения при выходе из блока. Это не проблема и не сложность.

Было бы славно, если бы в ответ на примеры кода @Andrey_Solomatin вы бы привели идентичные примеры на паскале. Тогда бы уже и можно было бы рассуждать о том, избыточен ли синтаксис питона или нет.

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

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

Не надо забывать, что Паскаль - язык с ручным управлением памяти. И в нем реализован механизм подсчёта ссылок, которого достаточно.

Ручное управление памятью, как правило не способствует читаемости кода. Наверное, только Rust сумел выделиться на этом поприще.

Твой defer из Zig, в Паскале реализуется парой строк и выполняет полностью всё то, что в Zig.

procedure TForm5.Button1Click(Sender: TObject);
begin
  var Test := TTestList.Create;
  defer(Test.Free);

  Test.Add('1');
  Test.Add('2');
  Test.Add('3');
  Test.DoRaise; //генерируем исключение
  Test.Add('4');
end;

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

Пример реализации
unit System.Defer;

interface

uses
  System.SysUtils;

type
  IDefer = interface
  end;

  TDefer = class(TInterfacedObject, IDefer)
  private
    FProc: TProc;
  public
    constructor Create(Proc: TProc); reintroduce;
    destructor Destroy; override;
  end;

function defer(Proc: TProc): IDefer; inline;

implementation

function defer(Proc: TProc): IDefer;
begin
  Result := TDefer.Create(Proc);
end;

{ TDefer }

constructor TDefer.Create(Proc: TProc);
begin
  inherited Create;
  FProc := Proc;
end;

destructor TDefer.Destroy;
begin
  if Assigned(FProc) then
    FProc();
  inherited;
end;

end.

Речь не о возможностях языка, а о читаемости.

Говорить о читаемости не используя всех возможностей языка смысла нет.

Явное открытие и автоматическое закрытие с контекстным менеджером это как раз о читаемости. Прочитав первую строчку сразу понятно, что не надо дочитав секцию до конца проверять, закрыли ли объект.

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

С темы литералов для коллекций в плавно съехали. Вот например в джаве это целый цирк с конями. https://stackoverflow.com/a/6802502/1310066


TFile.ReadAllLines(path)[0]
Засунуть файл целиком в память ради первой строчки это пять!

Это простой пример. Реализовать и использовать то что нужно труда не составит. Это просто сахар, как и ReadAllLines. Реализуй чтение файла по строчно через генератор, или сделай функцию чтения конкретной строки. Это что, сложно? Нет. Там писать-то ничего и не надо почти.

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

Это сахар, который есть уже сейчас. Написать продольного сахара можно тонну.

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

Автору спасибо,
А что за линтер такой, что проверяет на неизменяемость(immutability)?
Мне тоже не нравится система типов, типа :D :

class Class1:
  @staticmethod
  def build() -> "Class1":  # Господи, за что ?
      pass

Так это, Self вроде уже завезли, не? https://peps.python.org/pep-0673/

class Class1:
    @staticmethod
    def build() -> Self:

Ну и статикметоды были ошибкой (по мненю Гвидо), классметоды рулят :)

We all know how limited static methods are. (They’re basically an accident — back in the Python 2.2 days when I was inventing new-style classes and descriptors, I meant to implement class methods but at first I didn’t understand them and accidentally implemented static methods first. Then it was too late to remove them and only provide class methods.

Так это, Self вроде уже завезли

Так то в 3.11, А у нас часть продакшена на 3.7 ешшо ;( . Ну и тоже, почему не сделать просто имя класса без кавычек, зачем вводить Self?

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

Аргумент так себе. Если вы меняете имя класса в ручную на свой страх и риск, то да.

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

Для вас есть typing_extensions

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

Так добавьте первой строчкой

from __future__ import annotations

А что так можно было ? :palm_face: :D Спасибо, не знал

зачем вводить Self?

На самом деле для использования в иерархии классов. До Self вот так приходилось.

from __future__ import annotations

from typing import TypeVar


ClassType = TypeVar('ClassType', bound='A')

class A:

    def __init__(self) -> None:
        self._x = 0

    def func(self: ClassType, x: int) -> ClassType:
        self._x = x
        return self


class B(A):

    def __init__(self) -> None:
        super().__init__()

    def func(self: ClassType, x: int) -> ClassType:
        return super().func(x)


def main() -> None:
    b = B()
    print(type(b.func(1)))


# Press the green button in the gutter to run the script.
if __name__ == '__main__':
   main()

Кстати, насколько нормально аннотировать self в аргументах функции класса? Имею ввиду такой вот случай:

from typing import Self


class A:
    x: int = 0

    def func(self: Self) -> None:
        self.x += 1

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

Self нужен в основном, чтобы аннотировать classmethod'ы - этот type hint означает, что возвращается инстанс текущего класса:

@classmethod
def foo(cls, param) -> Self:
    return cls(param)

Никогда не видел, чтобы кто-то аннотировал self

Плюсик посту за сломанную вёрстку)

4 дня прошло, до сих пор не пофиксили. Прям странно.

TL;DR: "Мой простой Питон оказался нифига не таким простым, да как так то, а?"

{} # это не пустое множество, это пустой словарь

Множество - это урезанный словарь, у которого есть только ключи, и их достаточно, поэтому при создании пустого объекта нужно создавать полный объект, т.е. словарь, а не множество

1 # это просто число (int)

(1) # это тоже просто число - да, чтобы поддерживать вычисления в формулах
(1, 2) # это кортеж (tuple)
1, # это тоже кортеж
- да, и это элегантно (запятая как элемент перечисления), можно обойтись без скобок
() # это пустой кортеж
- да, и соотносится с функциями без аргументов
(1+2) # это число
- да, чтобы поддерживать вычисления в формулах
tuple() # а это тоже пустой кортеж

[] # это пустой список

[1] # это список с одним элементом - да, запятая не нужна
[1, 2, 3] # это список с 3 элементами
[i for i in range(3)] # это тоже список с 3 элементами
() # это пустой кортеж
(1) # это просто число
- да, чтобы не путать с формулами, обозначьте запятой перечисление
(1, 2, 3) # это (ха-ха) кортеж
- к чему относится ха-ха? )))
(i for i in range(3)) # это не кортеж, это генератор :D
- да, и это отличает его от списка (в том смысле, что список содержит все сразу, а генератор выдаёт по одному). А в кортеже по логике, не может быть цикла, ибо кортеж неизменяем, а цикл предполагает изменяемый список - создал первое значение, добавил второе и т.д.

Я смотрю на этот список и прям слышу 'let's talk about JavaScript'...

8-)

Всё это говорит, что нам срочно нужно ещё минимум три вида скобок. () [] {} - недостаточно.

Безумие с <> как скобками в C++ и потомках - подтверждает это.

Многие подобные вещи имхо надо рассматривать в историческом контексте. И питон и, например, джанго развивались параллельно и быстрыми темпами.

Мой первый проект на джанго датируется 2007-м годом. Тогда джанго была еще в альфа версии, и там еще не было 90% современных возможностей и батареек.

И если кто-то из 2023-го года взглянет на тот мой проект (а он работает до сих пор, облепленный костылями), то придет в ужас, задавая вопросы типа: а почему здесь не использовано наследование моделей, а почему здесь не использованы методы классов, почему для работы с деревьями используется кривой самопис, а не взят готовый джанговский модуль...??? А ответ прост - не было этого ничего в 2007 году: пайтон был еще в первой и второй версиях, джанго - в альфа версии и т.д. Ну а дальше - груз совместимости.

О, да, пробовал я както в Django добавить Websockets и Async, соответственно... Это был ад танцы с бубном, т.к. часть кода - синхронна, а часть - асинхронна. А для кеша вообще пришлось писать свой модуль, склеенный из двух других (синхронного и асинхронного), т.к. ни тот ни другой не могли нормально работать с sync_to_async / async_to_sync...

А ещё, забытая запятая в конце строки с присваиванием (упомянутое в статье) может подарить тонны WTF??? при попытках разобраться, откуда тут кортеж? И только опыт лишает или сокращает это удовольствие.

a = 42,

Опциональные скобки - это зло. Хотя часто это удобно.

У меня достаточно часто такие вещи ловятся статическим анализатором. Но для этого надо расставлять аннотации по проекту.

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

При рефакторинге - запросто

А пример можно?

Очень искусственный пример. До рефакторинга:

def func():
    result = {
        "speed": 42,
        "coord": (1, -2),
    }
    return result

После рефакторинга:

def func():
    # редактируем методом копипасты и удаления лишнего
    speed = 42,  # забытая запятая
    coord = (1, -2)
    return {
        "speed": speed,
        "coord": coord,
    }

Аннотации типов могут помочь, но в процессе, особенно на начальных этапах рефакторинга, можно просто не обратить внимания или забить на то, что IDE что-то там подчеркивает.

Удивили. Мне, испорченному PyCharm'ом, в голову бы не пришло.

Вдогонку, не понимаю, как бы тут могли помочь аннотации без замены возврата на, например, dataclass.

Мои пять копеек: в itertools нет эффективного итератора по части списка.
Допустим, нужно посчитать сумму элементов с 10 по 20.
1. sum(my_list[10:21]) - лучший вариант, но он тратит доп. память
2. sum(itertools.islice(my_list, 10, 21)) - не эффективно по времени, т.к. начинает перебирать элементы с 0
3. Эффективно, но уродливо:
sum_ = 0
for i in range(10, 21):
sum_ += my_list[i]

Вот объясните мне, почему п. 3 это уродливо? Я слышу это уже лет 10 в контексте разных языков программирования. Но этот код же элементарно читается, понятно что он делает, в большинстве ситуаций он будет самым эффективным. Что в нём такого плохого?

Очевидно же:

  • занимает 3 строчки вместо 1

  • читается дольше чем варианты 1 и 2

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

def list_iterator(list_, start=0, stop=None, step=1):
    if stop is None:
        stop = len(list_)
    for i in range(start, stop, step):
        yield list_[i]


sum(list_iterator(my_list, 10, 21))

занимает 3 строчки вместо 1

тогда почему бы не записать в одну?

sum(my_list[i] for i in range(10, 21))

занимает 3 строчки вместо 1

читается дольше чем варианты 1 и 2

Т.е. стоит играть в code golf с с каждой функцией? Может и классы не писать, не закладываться под расширение. Большая часть паттернов приводит к увеличению количества кода. Ну и покажите мне того человека которому сложно прочитать 3 строчки кода? Я никогда этого не понимал и продолжаю не понимать.

Если б вы пытались понять так же усердно, как передергиваете мои аргументы, то может и получилось бы

А как же следующее?

sum(my_list[i] for i in range(10, 21))

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

соглашусь, отличное решение

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

Третий вариант норм, нет в нём ничего ужасного. Если нужно часто, напишите свою функцию.

Справедливости ради mutable default, который определяется в момент создания функции, позволяет коротко писать функции, которым нужно что-то хранить между вызовами:

def do_some(x, *, _counter=[0]):
    _counter[0] += 1
    return x, _counter[0]

print(do_some('foo'))  # ('foo', 1)
print(do_some('boo'))  # ('boo', 2)
const do_some = (() => {
    let _counter = 0;
    return (x) => {
        _counter += 1;
        return [x, _counter];
    };
})();

console.log(do_some('foo'));  // ['foo', 1]
console.log(do_some('boo'));  // ['boo', 2],

Без этого нужно либо делать глобальную переменную, либо делать «фабрику», замыкание, класс или ещё что-то подобное.

Мне вот в питоне больше всего не хватает удобных распаковок словарей. То, что в js коротко:

const d = {foo: 1, boo: 2, zoo: 3};
const {foo, zoo} = d;

в питоне это громоздкое

d = {'foo': 1, 'boo': 2, 'zoo': 3}
foo, zoo = d['foo'], d['zoo']

Можно сделать вот так:

d = {"a": 0, "b": 1}
a, b = d.values()

или

d = {'foo': 1, 'boo': 2, 'zoo': 3}
foo, zoo, _ = d.values()

Тут можно здорово налететь, если словарь приходит откуда-то ещё, и содержит ключи в другом порядке.

Вот так можно:


>>> d = {'foo': 1, 'boo': 2, 'zoo': 3}
>>> foo, boo, zoo = d.values()
>>> print(foo,boo,zoo)
1 2 3

Еще есть **.

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


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

это у Алекса стилистика такая. Тут не надо удивляться, просто наслаждайтесь.

@kesn - спасибо в очередной раз, услада для мозга. Хотя ты знаешь, про генераторы я с тобой не согласен, тот же пропуск элемента или как рапараллелить асинк генератор. А про вложенный break - как останавливать внешние циклы, если они организованы генераторами тоже уже есть на habr.

Интересно было почитать, но добавлю пару слов в пользу питона.

Питон - очень офигенский язык: у него простой синтаксис, по нему много материалов, он много где применяется, есть много библиотек и он очень хорошо сочетается с другими языками, что позволяет нивелировать его минусы, т.е. я могу спокойно писать расширения на с/с++ и юзать их из питона с высокой скоростью и этого, в свою очередь, хватит для большинства задач, а если нет, то могу тестировать прототипы на питоне, а потом всё выводить в прод на тех же самых плюсах.

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

у него простой синтаксис

По сравнению с чем?

Он простой скорее в значении "ненагруженный"; обычно есть единственно правильный способ что-то записать, когда в других языках есть и несколько способов объявить функцию, ставить/не ставить фигурные скобки (и сами скобки нагружают), разные отступы между всем чем можно и прочее.

С php например

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

Интересно, для меня одного

код проще для восприятия

в случае, если в нём явно объявлены типы данных?)

Хорошо подмечено). Дело в том, что в питоне также есть аннотация типов, но она в отличие от низкоуровневых ЯП на производительность не влияет и как раз нужна для лучшего восприятия.

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

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

Как аннотации типов влияют на производительность в низкоуровневых ЯП? Раскройте мысль, пожалуйста.

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

НЛО прилетело и опубликовало эту надпись здесь

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

Например, эта разница хорошо видна при использовании разных интерпретаторов: если использовать Cython вместо Cpython, то код в основном будет отличаться только указанием типов переменных, а скорость увеличится в разы.

НЛО прилетело и опубликовало эту надпись здесь

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

А то. Ведь у Вас замкнуло - Вы пытаетесь минимизировать энергетические затраты мозга на понимание вызывающими гормональное поощрение методами.

Захотелось убрать под спойлер

Классика: если я не понимаю, значит я глупый -> если я не понимаю, значит это неубедительно -> если я не понимаю, значит это неверно. Достигнуто: если я не понимаю, значит я самый умный.

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

Всё едино - Эппл так же хейтят. Но преимущество Эппл в умении не обращать внимание на собаку будучи караваном. А несчастные типа Python вполне могут понаделать глупостей, и действительно глупости делают.

Как я такое замечаю? Да по замене "я хочу достичь этого" на "я хочу сделать так".

Кому не нравится пост - обломайтесь. Моя карма, как хочу так и трачу (за Интернет уплачено, развлекаюсь как моей душеньке угодно).

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

Скажите это тоннам легаси аля 2.2-2.7, которые работают уже лет пятнадцать (сам не верю этой цифре... как время то пролетело)
PS пример с прода, чтобы не быть голословным https://github.com/SpriteLink/NIPAP

Что поделать, тогда видимо был хайп.

Согласен с вашими словами кроме продукта: Cython, Numba, библиотеки типа itertools, numpy, pandas, joblib, asyncio и расширения на других ЯП в совокупности позволяют добиться хорошей производительности, а этого хватит чтобы написать средних объемов сайт или неплохую рекомендательную систему.

Да, для беспилотников и прочих задач, где нужна максимальная скорость, питона не хватит, но для задач попроще - с головой, например, большая часть кода ml-библиотеки sklearn написана на Cython и этого вполне хватает чтобы учить модели на данных конских размеров, учить нейросетки на питоне - одно удовольствие, тестеры и девопсеры тоже пишут на нём скрипты. Так что всё не так плохо)

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

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

Я могу на плюсах написать любое нужное расширение и импортировать его из питона, получив такую же скорость. Можно с помощью Cython или numba прямо на питоне тащить high-load прод, единственное - это computer vision или очень большие сервисы: в этих задачах очень часто питон используется для прототипирования, а в прод всё или почти всё тащится на низкоуровневых языках, но это действительно интересные и сложные задачи, но таких от общего количества не очень много.

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

НЛО прилетело и опубликовало эту надпись здесь

Если честно, мне до опытного программиста далеко, так что какие-то вещи я пока не понял, НО
Вот там, где я понял, не могу не согласиться. А примеры с Calendar и символьным адом, так вообще непроизвольно вызвали у меня смех.
В общем, статья длинная, новичкам не вполне понятная, но определённо познавательная, спасибо!)

Тут всё понятно, если обратится к истории. Питон создавался чтобы быстро и интересно слеплять крестовые библиотеки. Штуки, которые слишком сложны для баш-скрипта, но до полноценного проекта не дотягивают. А потом язык и Гвидо были взяты на вооружение гуглом и там стали делать из питона джаву. Добавили ABC, исключения стали исключениями (раньше можно было кидать что угодно), аннотации типов. Всё что нужно для "настоящего" проекта ПО. Результат — текущее состояние, где соседствуют старый и новый подходы.

Гугл ставили опыты над питоном буквально несколько лет. Пока не поняли, что сделать из уже ежа не выйдет. Наработки были выброшены на всеобщее обозрение в виде патчей, из которых были приняты лишь несколько штук. А Гугл потом сделали по мотивам Go и взялись за JavaScript, получив V8.

Очень рад видеть очередной кайфовый пост от @kesn — и и покачать головой, и посмеяться, и что-то новое узнать. Спасибо, не болейте!

Хорошая работа. Из свежего можно было добавить pattern matching, но здесь поводов для глумления хватит на отдельный пост.

Основная проблема питона в том, что они тащут в язык и стандартную библиотеку абсолютно сырые вещи. Вероятно чтобы потом, с помощью сообщества, доделать как надо. Но это 'как надо' никогда не наступает потому, что сообществу в первую очередь нужны не эти ваши эксперименты, а чтобы при обновлении ни чего не ломалось. Иными словами - обратная совместимость. Поэтому все эти уродцы, единожды попав в питон, остаются в практически неизменном виде там на многие годы.

А было бы интересно про pattern matching. По-моему крутая вещь. Отлично что ее взяли из раста. И там она очень даже к месту.

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

На питоне не пишу, но, лично для меня джависта, было удивлением, что переменная родительского класса и наследника - это не одна и та же область памяти.

было удивлением, что переменная родительского класса и наследника - это не одна и та же область памяти.

Вы могли бы пример кода привести?

Кажется я перепутал с поведением поля экземпляра. Но всё равно для меня это выглядит очень странно

class What:
    field = 1

a = What()
print(a.field) // 1
a.field = 2
print(a.field) // 2
del(a.field)
print(a.field) // 1

PS: Сейчас прочитал, что это переменная класса, а не экземпляра. Ладно, я не знаю питон)

В вашем коде изначально и в конце - она. А после присваивания двойки - таки экземпляра. Поменяйте print() на

print(a.field, id(a.field), What.field, id(What.field))

и все увидите.

Вдогонку, а последний PyCharm 2023.2 врет. Но issue лень оформлять.

Язык старше Java, что вы от него хотите.

В отличие от Java, у питона случился переход 2->3 с довольно значительными поломами обратной совместимости, тут нет непрерывности.

Зато сейчас требуется устанавливать аж 3 jvm, если вы используете какое-нибудь копроративное поделие вроде апплета для BMC: актуальную, с поддержкой современного софта, устаревшую, в которой работает несовременный, и еще одну для старых извращенцев, не осиливших за охреневшие бабки и 17 лет переехать на что-нибудь посвежее..

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

  1. Зачем?

  2. async def empty_agenerator(): return; yield

  1. Реализуем интерфейс, в котором есть этот генератор. Наша реализация не должна ничего генерировать.

  2. Не пройдет статический анализ:

    1. unreachable code

    2. yield требует указать значение, если генерируемый тип не включает None

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

Но как правило, асинхронного итератора вполне достаточно. Встроенного способа создания пустого асинхронного класса-итератора, я не нашёл, но его с лёгкостью можно написать самому:

from typing import AsyncIterator

class EmptyGenerator(AsyncIterator):
    __slots__ = ()

    def __aiter__(self):
        return self

    async def __anext__(self):
        raise StopAsyncIteration

Спасибо, выглядит отличным решением. В интерфейсах использовать более общие AsyncIterator[...], а для реализаций написать фабрику типизированных пустых асинхронных итераторов.

Если вдруг кто не смог прочитать очень длинную cut строчку (на старой версии сайта её прочитать нормально нельзя):


Это что же получается, kesn опять открыл postman и сломал вёрстку на сайте? Поразительно, никогда такого не было, и вот опять! В принципе, тут можно писать текст любой длины (похоже, у них на бэкенде не Char(255), а Text). Они проверяют длину только на фронтенде, а бэкенд принимает строку любой длины. И это, блин, забавно) Вообще мой девиз — 'кто ищет, тот всегда найдёт', поэтому я ищу постоянно. Кстати, на Хабре скоро выйдет статья про программирование глазами Погромиста, там в том числе про уязвимости на сайтах будет — поэтому если не хотите пропустить, то подписывайтесь на меня в телеге: @blog_pogromista

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

Скриншот того, как это выглядит у меня:


CSS is awesome

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

Как педант, педанту, в Питоне всё рантайм.

В общем, питон - динамический. Очень динамический. Даже слишком. По
моему опыту, в 99% случаев мне эта динамика вообще не впёрлась - я знаю,
какие где типы ожидаются и какие атрибуты у моих классов, но я всё
равно плачу за "гибкость" питона. Плачу скоростью выполнения кода и
количеством ошибок.

На этой магии всё остальное работает. Иногда смотришь внутрь и удивляешься.

Как вы думает, что такое NamedTuple в этом примере из документации? https://docs.python.org/3/library/typing.html?highlight=namedtuple#typing.NamedTuple

class Employee(NamedTuple):
    name: str
    id: int

https://github.com/python/cpython/blob/main/Lib/typing.py#L2770

Недавно я осознал, что если функция "чистая", то есть не модифицирует входные аргументы, то такой код абсолютно нормальный:

Не совсем. Можно поменять аннотацию list на typing.Sequence. Мы по прежнему можем передать list в качестве аргумента, на как только начнем его мутировать внтури, статический анализатор запоёт.

def foo(var: int, checks: Sequence[Callable] = []):
	for check in checks:
		check(var)

Ну тогда уж можно передавать пустой tuple - и аннотация Sequence будет верная, и мутировать не получится

Я мог неверно понять автора, но разве речь не про:

def foo(var: int, checks: list[Callable] = list()):
	for check in checks:
		check(var)

Автор поднимает вопрос, что не все могут ответить, что выведется при втором вызове foo.

def foo(var: int, checks = list()):
	print(checks)
	checks.append(var)


foo(1)
foo(2)

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


Я пробовал читать документацию про это, там всё очень сложно запутанно. Для себя использую только звёздочку. Аругменты в функции позиционные, но вызывать нужно как именные, а то будет ошибка в рантайме.

def foo(*, bucket, key):
    ...

foo(bucket="key", key="bucket")
foo(key="bucket", bucket="key")

Все эти "недостатки" высосаны из пальца и не имеют большой значимости.

Ты учишь 1 раз непонятные места, и потом всё хорошо кодится и работает.

Тот же С++ ты учишь 10 раз проблемные места, и всё равно стреляешь себе в ногу.

Из всех ЯП питон для меня топ.

Жаль, что развитие замедлится если примут nogil. Оказывается есть куча маглов, которым GIL мешает.

Хочешь ещё поугорать – начни изучать Common Lisp. Лет через 10 сможешь и про него такую же статью написать, даже пожалуй похлеще :)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории