Основные недостатки языка Python

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


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


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


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


Но не всё с этим языком легко и просто. Помимо специфического архитектурного решения под названием GIL, воплощённого в основном интерпретаторе языка CPython и связанных с ним проблем с эффективностью многопоточности, Питону присущи и более фундаментальные недостатки, сильно ограничивающие область его применения.


Скорость


Основным среди таковых принято считать его медлительность, хотя это с определённой долей справедливости парируется тем, что скриптовому языку скорость особо и не нужна. В задачах, требующих высокой производительности, он выступает лишь как обёртка для манипуляций с API низкоуровневых библиотек, написанных на языках с поддержкой AOT-компиляции. Самыми популярными из таких языков на данный момент являются, конечно же, C и C++. На первом, например, реализована широкоиспользуемая библиотека NumPy, созданная для математических операций с массивами произвольной размерности. На втором — набирающий популярность фреймворк для обучения нейросетей PyTorch.


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


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


  • Использование Python в качестве языка командной строки и управления взаимодействия между другими программами. Здесь он имеет определённое преимущество перед Bash, Sh и прочими языками оболочки, лишёнными удобной работы с массивами и строками и имеющими плохочитаемый синтаксис. Тем не менее описывать конвейеры в последних намного удобнее. Пример: управляющие скрипты большинства дистрибутивов Linux.
  • Конфигурация и управление базами данных. Примеры: модуль sqlite3, Django и т.д.
  • Манипуляция потоками вычислений, в которых расчёты производятся глубоко оптимизированным кодом, скомпилированным в непосредственные инструкции процессора. Python тут выступает лишь как среда для взаимодействия с API по инициализации и описанию структуры взаимодействия этих потоков. Примеры: NumPy, CuPy, PyTorch и т.д.

Почему Python медленный


Тут есть два основных фактора:


  • В нём практически всё аллоцируется на куче.
  • Перед выполнением операций с любым объектом интерпретатор проверяет его тип.

Давайте взглянем на примерную реализацию структуры в коде CPython, которая является обязательной частью всех питоновских объектов:


typedef struct _object {
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;

Переменная ob_refcnt отвечает за подсчёт ссылок, указывающих на данный объект. Если их количество становится равным нулю, это становится сигналом для сборщика о том, что память, занимаемую этим объектом, можно освободить. ob_type указывает на тип данного объекта. Плюс к этому, если объект владеет сущностью нефиксированного размера, указатель на тип дополняется указателем на область памяти, где эта сущность хранится, и переменной, отвечающей за её размер.


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


Что касается языка Python, то он компилируется в байт-код, а не в инструкции процессора. Значит, предсказатель переходов тут становится бессилен.


Оптимизации векторизации тут тоже не работают. Как пример, обычный питоновский массив вида [100, 200, 300, 400, 500], на самом деле, как было показано выше, хранит не целые числа, а указатели на объекты типа int, каждый из которых хранит указатель на область памяти, в которой записано соответствующее ему число. Даже один косвенный указатель ломает векторизацию, а тут для доступа к информации о числе нужно пройтись, как минимум, по двум. Лиха беда начало, перед каждой операцией с объектом интерпретатор Питона должен перейти в область памяти с реализацией соответствующего метода для текущего типа, и переход этот делается опять же по указателю.


Динамика


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


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


  • Во-первых, это динамическая типизация


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


    Писать что-то подобное возможно только в динамически типизированных языках:


    >>> x = 3
    >>> x = '36'
    >>> 
    >>> def foo(a, b):
    >>>     if b:
    >>>         return [a]
    >>>     return a
    >>> 
    >>> foo(x, True)
    ['36']
    
    >>> foo(x, False)
    '36'
    
    >>> class Bar:
    >>>     __slots__ = ('baz',)
    >>> 
    >>> x = Bar()
    >>> x.baz = 332
    >>> x.baz
    332
    
    >>> x.baz = 'Some string'
    >>> x.baz
    'Some string'
    
    >>> foo(x, True)
    [<__main__.Bar at 0x10ff0d700>]

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


  • Вторая такая особенность — это пространство имён.


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


    >>> a = 5
    >>> b = True
    >>> 
    >>> if b:
    >>>     x = 100
    >>> x
    100
    
    >>> for i in range(10):
    >>>     for j in range(5):
    >>>         pass
    >>> print(i, j)
    9 4

  • Третьим фактором является широкая гибкость в динамическом переопределении процесса создания объектов. В Питоне возможно такое:


    >>> class Meta(type):
    >>>     def __new__(cls, name, bases, attrs):
    >>>         if list in bases:
    >>>             return super().__new__(cls, name, (tuple,), attrs)
    >>>         return super().__new__(cls, name, (list,), attrs)
    >>> 
    >>> class Foo(list, metaclass=Meta):
    >>>     pass
    >>> 
    >>> class Bar(tuple, metaclass=Meta):
    >>>     pass
    >>> 
    >>> issubclass(Foo, list)
    False
    >>> issubclass(Foo, tuple)
    True
    >>> issubclass(Bar, tuple)
    False
    >>> issubclass(Bar, list)
    True

    Этот код требует пояснения. Дело в том, что в Питоне все сущности, существующие в рантайме, являются объектами. Объекты, в свою очередь, являются реализациями, или как их ещё называют — инстансами, классов. Но классы же ведь тоже являются объектами.


    Как вы можете догадаться, те сущности, инстансами которых являются классы, называются метаклассами.


    В данном конкретном примере классы Foo и Bar имеют общий метакласс Meta, который подменяет тип, от которого пытается наследоваться класс. Так, при попытке отнаследоваться от типа list, класс, на самом деле, станет наследником типа tuple, а в противном случае — типа list.


    Подробнее о метаклассах вы можете почитать тут.


  • Ну и наконец, четвёртой сложностью является возможность создавать классы динамически.


    >>> from collections.abc import Iterable
    >>> 
    >>> def wrap_class(cls):
    >>>     if issubclass(cls, Iterable):
    >>>         class FooCounter(cls):
    >>>             def count_foo(self):
    >>>                 return sum(1 for item in self if item == 'foo')
    >>> 
    >>>         return FooCounter
    >>>     raise TypeError(f'Class {cls} is not an iterable type')
    >>> 
    >>> wrap_class(list)([2, 3, 'foo', 'bar', 'baz', 'foo']).count_foo()
    2


Все эти особенности негативно влияют на возможность предоставления статических гарантий относительно поведения кода на языке Python. Существующие анализаторы, основным из которых является MyPy, нельзя назвать законченными. Особенной проблемой является то, что на данный момент они имеют крайне ограниченную функциональность при анализе случаев, подобных взятым из последних двух примеров. Ситуация осложняется тем фактом, что, несмотря на все те правильные инициативы, связанные с внедрением модуля typing в предыдущих релизах, система аннотации типов в Питоне остаётся невыразительной. Например, для указания интерфейса типа возвращаемого аргумента функции wrap_class из последнего примера требуется поддержка типов-пересечений, которой на текущий момент нет.


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


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


Выводы


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


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


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

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

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

    +7
    Названы 2 недостатка, которые по сути являются достоинством языка.
      +4

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


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

        +2
        Вы сами назвали их «особенностями». Их можно отнести как к киллер-фичам, так и к недостаткам.

        Но выносить это в заголовок как «недостатки»? Как-то недохайпово.
          0

          Я, честно говоря, опираюсь на следующий текст из статьи:


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

          Что вполне соответствует заголовку статьи

          0
          малое быстродействие

          Много ли вы делали проектов, где не хватало быстродействия именно Python кода?


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


          Пока еще не было случая, чтобы я уперся именно в нехватку быстродействия Python. Хотя у всех задачи разные, конечно.

            +4
            Много ли вы делали проектов, где не хватало быстродействия именно Python кода?

            Мой комментарий относится не к питону, а к, на мой взгляд, несправедливому осуждению автора статьи. Быстродействие и статический анализ никак не являются достоинством питона.
            Если они ни вам ни мне не мешают (как вы справедливо заметили), это их ещё не делает достоинством (коими они названы в первом комментарии).


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


            Хотя в данном случае не понятно о чём статья, тут вроде нет открывающих покровов истин или деталей.

              +3

              У меня на embedded-платформе воткнут labgrid (плагин для pytest). Так вот, набор тестов
              (10 файлов по 50 строк) стартует 20 секунд. В два раза быстрее стартует вся остальная операционная система с systemd, веб-сервером и прочим-прочим.


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

                –3
                Сам язык скорее всего тут не причём. Производительный код на нём писать можно это факт, то что обычно не пишут думаю да претензия возможная к языку(но даже так не факт).
                  +3
                  язык скорее всего тут не причём.

                  Язык никогда не бывает виноватым, виноват рантайм. Ну и что с того?


                  Бенчмарки нищеброда

                  hell.py:


                  print("Hello world")

                  time python3 ./hell.py
                  --
                  Hello wordl
                  
                  real    0m0.566s
                  user    0m0.432s
                  sys     0m0.042s
                  
                  time luajit ./hell.py
                  ---
                  Hello wordl
                  
                  real    0m0.033s
                  user    0m0.008s
                  sys     0m0.021s

                  Я понимаю, что сравнение некорректное, у python-а рантайм жирный, у Lua — никакой. Но даже при написании простых программ разница в запуске в целый порядок — это жуть

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


                  Похожее чувство было у меня когда узнал что Torch с Lua (проект развивался 15 лет) закрывают и одновременно facebook радостно продвигает «свой» PyTorch.
                  +2
                  Я делал 2. В одном ввели торнадо — и это, кроме того что не помогло, еще многое ухудшило.
                  Во втором перешли на раст.
                    +3
                    Я вот сколько работаю, а всё время получается, что либо алгоритм несовершенный, и на любом другом языке он не сильно быстрее бы работал, либо в базу все упирается, либо в сеть.

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

                      0

                      А в какой области задачи?

                        +1

                        От «распарсить логи и посчитать простенькую статистику по распаршенному» до «набросать proof-of-concept вычислительного кода, который плохо ложится на сишные numpy/etc».

                          0
                          Я правильно понял, парсер логов, в котором IO занимает меньшую часть работы? В память что ли весь загружается? На порядок-другой, то бишь минимум в 10-100 раз быстрее. До смерти любопытственно взглянуть на питоновскую версию.
                            +1
                            Я правильно понял, парсер логов, в котором IO занимает меньшую часть работы?

                            Да, nvme творит чудеса. Ну и, например, если это pcap-лог, и вам нужна какая-то нетривиальная логика обработки пакетов, то там уж независимо от IO будет всё медленно.


                            В память что ли весь загружается?

                            Да не, лениво стримится.


                            100 раз — это, конечно, скорее про вычислительный код.

                              0
                              В память что ли весь загружается?

                              попробуйте-ка на питончике сделать нормально mmap(). Намек понятен?

                                0
                                Намек понятен?
                                Неа. Не понял, какие выводы я из этого должен сделать.
                                  0

                                  mmap() позволяет очень сильно ускорить работу с файлами. Обратная сторона — это все требует отдельных функций для работы с такими файлами. Нативно в ЯП не получается это реализовать. Но вот NumPy в это умеет и это внушает определенную надежду, что на python можно писать относительно быстрый и относительно экономный с точки зрения памяти код.

                                    0
                                    mmap() позволяет очень сильно ускорить работу с файлами

                                    Я всё еще не понял, как mmap должен ускорить работу в данном случае. Задача: прочитать целиком большой файл, ничего писать не надо. Я предположил загрузить файл в память целиком. Как в этой ситуации mmap ускорит IO или сэкономит память?
                                      0

                                      Фейспалм. Когда Вы читаете файл fread(), Вы имеете следующее:


                                      1. читаете неоптимальными блоками файл в ОЗУ с накопителя,
                                      2. дублируете данные — т.к. операционная система должна сначала считать файл в кэш, потом скопировать эти данные внутрь Вашего процесса.
                                        Эффективность в районе плинтуса. С помощью mmap() Вы полностью перекладываете чтение файла на операционную систему, дублирования данных не происходит.

                                      Я предположил загрузить файл в память целиком

                                      дополню — а как Вы будете считывать 16ГиГ файл при доступной ОЗУ всего 1 ГиГ? Будем ждать, пока условный fread() в памяти программы сожрет 16ГиБ и свалится в swap? Или реализовывать нетривиальные алгоритмы чтения определенного скользящего окна?


                                      ОЧЕНЬ СТРАННО, что Вы не в курсе и даже не удосужились прочитать инфу по ссылкам

                                        0
                                        дополню — а как Вы будете считывать 16ГиГ файл при доступной ОЗУ всего 1 ГиГ?
                                        Я вообще говорил о гипотетической ситуации загрузки файла в память целиком одним системным вызовом. Очевидно, не стоит этого делать, если у вас памяти 1 гиг, а файл 16 гигов.

                                        Поэтому я и спросил, как загрузка через mmap() ускорит или сэкономит память, если mmap() загружает данные по требованию, а не сразу весь файл в память?
                                          0

                                          Подскажите, я ответил на Ваш вопрос, Вы согласны с указанными доводами? Или требуется дальнейшее обсуждение?

                                            0
                                            Подскажите, я ответил на Ваш вопрос, Вы согласны с указанными доводами?
                                            Я честно не понял, как mmap() поможет конкретно в ситуации, когда я загружаю файл целиком в память одним сисколом для чтения. Мне кажется, должно быть даже медленнее через mmap().

                                            Поэтому будет здорово, если объясните.
                        +2
                        Анализ нескольких временных рядов и их пересечений, например.
                        либо алгоритм несовершенный
                        Это скорее да, чем нет, но данные при этом принципиально ограничены. Усложнение алгоритма (добавление оптимизаций, кешей, неполные вычисления, мемоизация) — это время разработчика. При этом компилируемый ЯП может решить проблему без усложнений, оказавшись дешевле.
                        Жаль, ЯП, которые мне нравятся, неидеальны с GUI. Доминируют C++ и Python.
                          0
                          кешей… мемоизация

                          как минимум — трейд-офф большего потребления памяти, сложности на более быструю работу программы. Чудес не бывает. А потом — хопа, у нас память улетает, как в java.


                          это время разработчика.

                          все так.

                      +12

                      Основные недостатки молодых динамично развивающихся ™ авторов:


                      • желтые заголовки
                      • "развенчание" старых "мифов"
                      • создание новых мифов
                      +6
                      Во-первых, это динамическая типизация

                      Данное словосочетание означает тот факт, что переменная, объявленная в этом языке, не имеет привязанного к ней типа.

                      Точно именно это означает?
                      Что же тогда выведет этот код для произвольной переменной?

                      type(foo)
                        0

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


                        В Питоне вы можете делать следующее:


                        >>> foo = 1
                        >>> type(foo)
                        int
                        
                        >>> foo = "foo"
                        >>> type(foo)
                        str

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


                        Это приводит к тому, что для того, чтобы гарантировать, что в конкретном месте программы переменная foo имеет тип baz, то приходится писать бесконечные:


                        isinstance(foo, baz)

                        или


                        type(foo) is baz

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

                          +1
                          тип данных, хранящихся в переменной
                            +8
                            type(foo)

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


                            PEP484 ввел Type hints, которые указывают, что в конкретной переменной ожидаются объекты конкретного типа, но type hints не влияют на то, что происходит в рантайме. Интерпретатор практически полностью их игнорирует. И появились они в языке только в угоду тому, чтобы можно было сделать линтеры вроде mypy, которые их проверяют. Но при этом нет гарантий, что в переменную с type hint будет записан объект тип которого соответствует type hint.

                              +2

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


                              Здесь также можно почитать о том, как применять аннотации типов в Питоне.

                            +2
                            Мне кажется, желание везде навтыкать проверку компилятором соответствиятипов переменных, это просто модное увлечение. ИМХО, нужно объявить возможность динамически менять тип в рантайме клиллер фичей и оставить эту тему в покое. А то уберем динамическую типизацию, сделаем так, чтобы компилятор ругался на типы, и придется писать в пять раз больше кода и использовать зубодробительные паттерны, чтобы обойти это «удобство».
                            Полно языков со статической типизацией. Вот пусть они этим и занимаются. Питон не про это.
                              +2

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

                                +4
                                это просто модное увлечение
                                Просто проекты разрастаются, начинаются проблемы с соблюдением контрактов и попытки их решить. Это не только языков программирования касается. Все крупные динамические языки уже получили возможность таких проверок — это не мода, а системный запрос индустриальной разработки. То же можно сказать про XSD+ для XML, развивающийся OpenAPI и т.п. Мне кажется, карьера разработчика, где не приходит понимание, почему так в мире происходит (ссылка на моду — это форма отрицания), идёт не очень хорошо.

                                Добавлю к статье, что по-моему Python страдает от своей безграничной возможности monkey patching и святой уверенности многих разработчиков в наличии некоего особенного pythonic пути, решающую пластилиновые проблемы (в этом они расходятся с аналогично пластилиновым, но неидеологичным Javascript'ом).
                                  +2
                                  Это и есть киллер фича, но в ограниченной области применения. Когда есть ограничение от самой команды, что конкретно этот скрипт (или подпрограмма или лямбда или что еще) не станет больше, чем (например) тысяча строк.

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

                                  У автомобиля есть два недостатка — он не летает и не плавает.

                                    –5

                                    Как по мне, единственный недостаток, когда рефакторишь код, нужно соблюдать отступы…

                                      +9

                                      ..., а то бы зафигачил всё в одну строку и горя бы не знал.

                                        0
                                        Вы передергиваете контекст
                                          0
                                          И опубликовал в хабе «Ненормальное программирование».
                                            –1
                                            А что не так? В C++ всё-же удобней — перемешал блоки кода как надо, один if вынес из цикла, два переместил в цикл, скопипастил пару строк из другой функции, нажал Ctrl-s и clang-format автомагически всё выровнял красиво. А с Питоном такой трюк не пройдёт — сиди, выравнивай отступы руками.
                                              +2

                                              Конечно же в C++ скобки и точкозапятые переносятся с места на место не руками, а автоматически вслед за мыслью разработчика.

                                            +5
                                            Ну кому как. Я, например, считаю наоборот, отступы плюсом, который приучает сразу писать более читаемо. Если отступы начали сильно увеличиваться — значит рефакторинг свернул куда-то не туда и надо пересмотреть свое решение.
                                            –7
                                            Отступы там главный недостаток
                                              +8

                                              На мой взгляд, главный недостаток питона — это то, что их два.

                                                0
                                                Да, питон3 это бомба под питон. И ладно бы сделали бы только питон3 и на этом остановились, нет трагедия углубляется и расширяется.
                                                  0
                                                  А что там дальше ужасного происходит?
                                                    –1
                                                    Добавляют все больше и больше спорных и не особо нужных фишек в язык. Все дальше и дальше от принципов питона.
                                                      +1
                                                      У вас есть публикации на Хабре… Не хотите про Питон что-то написать? )
                                                        +2
                                                        Добавляют все больше и больше спорных и не особо нужных фишек в язык
                                                        Ненужных для вас или для кого?
                                                    +1

                                                    Java смотрит на это с недоумением.

                                                    +4
                                                    Помимо специфического архитектурного решения под названием GIL
                                                    будете громко смеяться, но нет, это «явление» не является чем-то специфичным именно для Python, это зачастую незаменимый механизм обеспечения консистентности поведения интерпретаторов, иначе его бы назвали GPIL, Global Python Interpreter Lock или GPL, Global Python Lock
                                                      0
                                                      Кстати, используя multiprocessing можно вполне жить и с GIL.
                                                        +1

                                                        Угу, угу


                                                        В питоне удобно сделали, совместили параллелизм зелёных потоков и накладные расходы нативных тредов.
                                                      +2
                                                      Странно что никто не обсуждает ГИЛ и псевдоассинхронность.
                                                      Никто не вспомнил о проблемах юникода в третьем питоне.
                                                      Никто не замечает что за последние несколько лет в питоне не осталось вещей, которые можно делать только «одним правильным способом» — что противоречит Зен. Вообще, сколько от Зена осталось правды?
                                                      Фанктуул действительно превращают в мусорку. Раньше про это только шутили.
                                                        +1
                                                        Вообще, сколько от Зена осталось правды?

                                                        Никогда Python не был тем, о чём пишут в его дзене. Что значит "явно"? В Python хватает своих подводных камней, да и вообще сами понятия "явный/неявный" — это как "хороший/плохой" субъективны и относительны, лучше вообще их не употреблять. Всегда можно было сделать что-либо несколькими "правильными" способами. Один "правильный" способ — это миф, непонятно зачем тиражируемый.


                                                        Например, как в Python можно сделать singleton? С помощью декоратора, с помощью метакласса, с помощью объекта класса, с помощью обычного модуля — любой объект модуля есть singleton. И как же в Python правильно сделать singleton? Да как угодно. Только не начинайте, пожалуйста, что singleton сам по себе — это неправильно. :)


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

                                                          –1
                                                          Сразу приведу железный аргумент (по крайней мере в контексте Python): если бы singleton был чем-то неправильным, объект None не был бы синглтоном, так же как и его аналоги в других языках (в основном NULL/Null/null/NIL/nil). Думаю, можно без преувеличения сказать, что этот синглтон лежит в основе Python, как один из фундаментальных кирпичиков (возможно, схожим образом ситуация обстоит и в некоторых других языках программирования). Например, любая процедура на самом деле является функцией, неявно возвращающей None
                                                            0
                                                            Никакой язык не был своей целью, к которой он стремился.
                                                            Однако наличие цели позволяет языку развиваться органично и предсказуемо.

                                                            То что большинство популярных языков со временем потеряли фокус и начали хватать все фичи подряд — плохой пример. Обычно именно перегруженность продукта является главной причиной почему клиенты переходят на более молодой и простой продукт.
                                                              +1

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


                                                              Даже для C выходят новые стандарты, хотя, несмотря на почтенный возраст, он не выглядит таким монстром, как современный C++.


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

                                                                0
                                                                > Практически любой язык развивается, впитывает в себя новые фичи постоянно, а потом умирает.

                                                                я не отрицаю эту часть.

                                                                Я говорю про процесс выбора что включать. Либо что-то хайповое. Либо что-то что соответствует духу языка.

                                                                С моей точки зрению плюсы (про с не знаю, но подозреваю что тоже) настолько растостел, что можно найти 4 хороших разработчиков, которые плохо понимают что делают другие.
                                                          +1

                                                          Все динамически типизированные языки страдают от быстродействия по сравнению статически типизированными (компилируемыми). И в интерпретаторе нету доступа к branch-prediction и всяким другим плюшкам. Но есть же примеры где на динамической типизации применялись оптимизаторы с целью ускорить выполнение кода. Javascript это интерпретируемый язык, также как и Python. Но есть V8 который делает рантайм оптимизации на основе уже выполненных кусков кода. Да, JS компилируется в машинный а не байткод, но это не исключает подхода с двумя компиляторами, основным и оптимизирующим. Почему такой подход не может быть реализован для Python?

                                                            0

                                                            Судя по интернету, это уже есть (PyPy meta-JIT). Философия python, как языка "с батарейками" играет злую шутку, когда просто слишком много чего надо тянуть

                                                              0
                                                              все еще лелею надежду что pypy убьет cpython и станет основой python4
                                                                0
                                                                не думаю, что это произойдёт раньше, чем будет стёрта граница между оперативной и постоянной памятью (очень не скоро)
                                                                  0
                                                                  P4S5 & XBOX уже в этом году (с поправкой на корону)
                                                                  за 2 года все игровые компы будут такие
                                                                  причем не только оперативка но и память видеокарты

                                                                  по слухам следующие поколение проф.видеокарт будут распаивать ssd прямо на карте. скорости будут под 10гбит (чуть больше чем консоли) потому что контроллер ссд будет в ядре. Сори 10гбит — с учетом упаковки, которая тоже будет железной
                                                                    0
                                                                    Больше чем в 2 раза меньше, чем у бюджетной DDR4.
                                                                    Близко, но нет
                                                              0

                                                              Хорошо. Давайте гипотетический пример. Предположим, я хочу писать быстро и одинаково неэффективно под разные архитектуры. Ваши предложения? Код на пайтоне — дешев, сердит и переносим.

                                                              –6
                                                              Есть ещё один недостаток. Высокая сложность. У меня нет никакого желания его учить: сложный, медленный, бесят отступы(у меня другие правила оформления структурности кода, одинаковые для ВСЕХ ЯП которыми пользуюсь), не удобный для пользователя(надо устанавливать дополнительный софт, причем разный для разных версий). Не каких плюсов у этого ЯП в упор не вижу…
                                                                +2

                                                                Попробуйте тогда c++

                                                                  –3
                                                                  Я использую высокоуровневый ассемблер в основном(UASM), мне на нём проще всего работать. А плюсы реже использую. А питон… ну да, ну да, миллионы мух не могут ошибаться (с).
                                                                  +2
                                                                  «Нет никакого желания учить язык» != «Высокая сложность»
                                                                  0
                                                                  В Питоне переменная, будучи объявленной, не обязана прекращать существование после выхода из родного блока отступов.

                                                                  Самый стрёмный из недостатков! :(
                                                                    +2
                                                                    Самый стрёмный из недостатков! :(
                                                                    Вас обманули, это не недостаток. Иначе бы не работал вот такой код:
                                                                    if a:
                                                                        b = 1
                                                                    else:
                                                                        b = 0
                                                                    print(b)
                                                                    

                                                                    Либо автор сам не понимает, о чем пишет, либо выражаться надо яснее.

                                                                    Да хоть в пхп попробуйте, там так же работает:
                                                                    if ($a == 1){
                                                                        $b = 1;
                                                                    } else {
                                                                        $b = 0;
                                                                    }
                                                                    print($b);
                                                                    
                                                                      0

                                                                      Человек после C/C++/Pascal/других языков привыкает к явному объявлению начала жизни переменной внутри блока, в котором она объявлена. А мне, к примеру, после lua, где любая нелокальная переменная отправляется в _G (глобальную таблицу), писать на питоне — значит бороться с рефлекторной перестраховкой "сначала объяви local, только потом используй"

                                                                        0
                                                                        А ещё в других языках переменная b объявленная в двух разных блоках, может иметь два разных значения (и два разных типа).
                                                                          +1
                                                                          Вы уверены, что это:
                                                                          1. недостаток
                                                                          2. питона
                                                                          ?

                                                                          Человек после C/C++/Pascal/других языков привыкает к явному объявлению начала жизни переменной внутри блока, в котором она объявлена.
                                                                          Ну поздравляю, вы сейчас говорите про статическую типизацию. Только причем тут питон? У него динамическая.

                                                                          писать на питоне — значит бороться с рефлекторной перестраховкой «сначала объяви local, только потом используй»
                                                                          Опять таки, вы уверены, что это «недостаток питона», а не особенность динамической типизации? Я пример с пхп зачем приводил?

                                                                          А еще, вы уверены, что вы не путаете блок отступов и область видимости? Потому что это разные вещи. У функций, например, область видимости своя, а у циклов или условий — нет.

                                                                          Так и скажите, я не люблю динамическую типизацию. А то получается, что лыжи плохие, потому что не едут по асфальту.
                                                                            –1

                                                                            Я не говорил про недостаток, я говорил про неудобство.


                                                                            Я пример с пхп зачем приводил?

                                                                            Честное слово, лучше бы не приводили. У меня от PHP ни одного приятного впечатления никогда и не было.


                                                                            Опять таки, вы уверены, что это «недостаток питона», а не особенность динамической типизации? Я пример с пхп зачем приводил?

                                                                            При чем тут вообще типизация, речь про область видимости объекта. После С/С++/js/добавьте по вкусу ожидаешь, что синтаксический блок создает новую область видимости, а в python это не так. Когда за один день работаешь с четырьмя языками сразу — легко забыться и начать использовать приемы одного языка в другом.

                                                                              0
                                                                              Честное слово, лучше бы не приводили. У меня от PHP ни одного приятного впечатления никогда и не было.
                                                                              Речь не о том, нравится или не нравится, а о том, что дело не в питоне, а в типизации (не прямо, но косвенно). О чем я и толкую.

                                                                              При чем тут вообще типизация, речь про область видимости объекта. После С/С++/js/добавьте по вкусу ожидаешь, что синтаксический блок создает новую область видимости, а в python это не так.
                                                                              Окей, этот же код привожу на JS, переменная при выходе из блока не уничтожается:

                                                                              if (a==1){
                                                                              	b = 1;
                                                                              } else {
                                                                              	b = 2;
                                                                              }
                                                                              document.write(b);
                                                                              

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

                                                                              Еще один пример JS (такой же пример используется в статье, но на питоне):
                                                                              for (var i = 0; i < 9; i++) {
                                                                                 document.write(i);
                                                                              }
                                                                              document.write(i);
                                                                              

                                                                              Смотрите ка! Тут тоже переменная i не уничтожается при выходе из блока. Python, PHP, JS (все с динамической типизацией) — везде одинаково работает. Как мне еще это написать, чтоб вы поняли наконец, что блок кода и область видимости — разные вещи, и что питон тут не причем?

                                                                              Этот же код НЕ будет работать на C++:
                                                                              for (int i = 0; i < 9; i++ ){
                                                                                  cout << i;
                                                                              }
                                                                              cout << i;
                                                                              

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

                                                                                0
                                                                                Еще один пример JS (такой же пример используется в статье, но на питоне):

                                                                                Именно поэтому в js ввели ключевое слово let

                                                                            0

                                                                            Мне нравится как сделано в Rust:


                                                                            let b = if ... {
                                                                                // ...
                                                                                1
                                                                            }
                                                                            else {
                                                                                // ...
                                                                                2
                                                                            };
                                                                            

                                                                            Потому что if — это выражение, и оно может возвращать результат вычислений как любое другое выражение при условии, что оно полное (if/elese). По моему, прекрасно.


                                                                            Также скоупы тоже могут возвращать результат:


                                                                            let a = {
                                                                                // ...
                                                                                res
                                                                            };
                                                                              0
                                                                              Потому что if — это выражение, и оно может возвращать результат

                                                                              25 лет назад в Ruby было точно так же, вообще-то.

                                                                                0
                                                                                в лиспе чутка раньше
                                                                            –2
                                                                            Вас обманули, это не недостаток. Иначе бы не работал вот такой код:

                                                                            Потому что это должно выглядеть вот так:


                                                                            b = 1 if a else b

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

                                                                              –1
                                                                              b = 1 if a else b

                                                                              Как-то мне не очень нравится.
                                                                                –1

                                                                                Мне тоже не нравится. Я бы предпочёл писать


                                                                                b = if a:
                                                                                        1
                                                                                    else:
                                                                                        0

                                                                                , но это не вписывается в синтаксис Python.

                                                                                  –1
                                                                                  b = int(a)

                                                                                  Это не то, что Вы хотите?
                                                                                    0

                                                                                    Да на самом деле нет, потому что, первый пример требует, чтобы a умело конвертироваться в bool, а второй — чтобы a умело конвертироваться в int, и эти реализации необязательно согласованы друг с другом. Ну и потом, это просто пример, вполне можно представить случай, когда в ветках then и else более сложные выражения.

                                                                                    0
                                                                                    Ну и в общем случае, для более-менее сложной логики, я предпочитаю что-то вроде:
                                                                                    logic = { <rule_1>: <case_1>,
                                                                                    <rule_2>: <case_2>
                                                                                    .....
                                                                                    a = rule_<N>}
                                                                                    logic[a]
                                                                                    
                                                                                      0

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

                                                                                        0
                                                                                        Почему терять ленивость? Если нужны сложные кейсы оборачиваем их или в лямбды в простейших случаях, либо в функции или методы.
                                                                                        Питон именно про это, использование питоновских типов для максимальной выразительности.
                                                                                        Если сделать так как предлагают, это будет провоцировать создание зубодробительных условных конструкций, которые нужно час разбирать с бумажкой, чтобы что-то понять. Это на мой взгляд худшее, что может быть.
                                                                                        Добавил: причем в большинстве случаев функции гораздо лучше чем лямбды:
                                                                                        — говорящее имя.
                                                                                        — Расшивается сложность.
                                                                                        Лямбды неудобочитаемы, как и все однострочники.
                                                                                    0
                                                                                    А так? ))

                                                                                    b = a and 1 or 0

                                                                                    +1
                                                                                    Потому что это должно выглядеть вот так

                                                                                    Мы тут область видимости блоков обсуждаем, а не идиоматичность кода. Вы бы еще сказали, что переменные не должны называться a и b, а должны иметь осмысленное имя.
                                                                                      –1

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

                                                                                        +1
                                                                                        Так это и есть про видимость блоков
                                                                                        Да, только это разные конструкции. Вы привели тернарный оператор, который работает совсем иначе. А автор писал вообще про другое. В его примере вообще цикл. Мой пример в тему, ваш — нет. А про циклы я уже наприводил кучу примеров на разных языках немного выше
                                                                                +4
                                                                                Когда я вижу статью про недостатки Python, я навожу курсор мыши на ссылку, шумно вдыхаю, закрываю глаза и мысленно проговариваю фразу:

                                                                                Ну, во-первых, он медленный.

                                                                                После этого быстро прожимаю ЛКМ, встряхиваю головой, открываю глаза, судорожно перебираю зрачками строки текста, пока не нахожу нужные. Мои веки немного опускаются, по телу пробегает приятное тепло.
                                                                                Я еще ни разу не ошибался.
                                                                                  –3
                                                                                  Мое личное мнение. Скорость питона больше не играет роли. А вот отсутсвие настоящей многопоточности — его убивает. При чем я говорю не только о десятикратной многопоточности (cpu) но и о тысячикратоной (gpu).

                                                                                  В принципе, медленных языков больше не существует. Все упирается в IO. Буть это интернет или длина пути електрона от кеша второго уровня до ядра.
                                                                                    0

                                                                                    Не все пишут код для серверов на Intel Xeon. А вот использовать быдлокод, написанный на питхоне — вынуждены все. Увы

                                                                                      +2
                                                                                      В принципе, медленных языков больше не существует. Все упирается в IO.

                                                                                      Лол, нет. Иначе бы тот же numpy писали на Python, а не на C и Fortran.

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

                                                                                  Самое читаемое