Почему так много питонов? Сравнение реализации Python
Опубликовано: 2022-03-11Питон потрясающий.
Удивительно, но это довольно двусмысленное заявление. Что я имею в виду под «Питоном»? Я имею в виду Python как абстрактный интерфейс ? Имею ли я в виду CPython, общую реализацию Python (и не путать с Cython с таким же названием)? Или я имею в виду совсем другое? Возможно, я косвенно имею в виду Jython, IronPython или PyPy. Или, может быть, я действительно зашел слишком далеко и говорю о RPython или RubyPython (а это очень разные вещи).
Хотя упомянутые выше технологии имеют общепринятые названия и часто упоминаются, некоторые из них служат совершенно разным целям (или, по крайней мере, работают совершенно по-разному).
За время работы с интерфейсами Python я столкнулся с множеством этих инструментов .*ython. Но только недавно я нашел время, чтобы понять, что они из себя представляют, как они работают и почему они необходимы (по-своему).
В этом руководстве я начну с нуля и пройдусь по различным реализациям Python, закончив подробным введением в PyPy, который, как я считаю, является будущим языка.
Все начинается с понимания того, что такое Python.
Если вы хорошо разбираетесь в машинном коде, виртуальных машинах и т. п., не стесняйтесь пропустить этот раздел.
«Питон интерпретируется или компилируется?»
Это обычная путаница для новичков в Python.
Первое, что нужно понять при сравнении, это то, что «Python» — это интерфейс . Существует спецификация того, что должен делать Python и как он должен себя вести (как и с любым интерфейсом). И есть несколько реализаций (как и с любым интерфейсом).
Во-вторых, нужно понимать, что «интерпретируемый» и «скомпилированный» — это свойства реализации , а не интерфейса .
Так что сам вопрос не очень хорошо сформирован.
Тем не менее, для наиболее распространенной реализации Python (CPython: написанный на C, часто называемый просто «Python», и, конечно же, то, что вы используете, если вы понятия не имеете, о чем я говорю), ответ: интерпретируется , с некоторой компиляцией. CPython компилирует * исходный код Python в байт-код, а затем интерпретирует этот байт-код, выполняя его по мере поступления.
* Примечание: это не "компиляция" в традиционном смысле этого слова. Как правило, мы бы сказали, что «компиляция» — это преобразование языка высокого уровня в машинный код. Но это своего рода «сборник».
Давайте рассмотрим этот ответ более внимательно, так как он поможет нам понять некоторые концепции, которые появятся позже в посте.
Байт-код против машинного кода
Очень важно понимать разницу между байт-кодом и машинным кодом (также известным как собственный код), что, пожалуй, лучше всего иллюстрируется примером:
- C компилируется в машинный код, который затем запускается непосредственно на вашем процессоре. Каждая инструкция указывает вашему процессору перемещать объекты.
- Java компилируется в байт-код, который затем запускается на виртуальной машине Java (JVM) — абстракции компьютера, выполняющего программы. Затем каждая инструкция обрабатывается JVM, которая взаимодействует с вашим компьютером.
Если очень кратко: машинный код намного быстрее, но байт-код более переносим и безопасен .
Машинный код выглядит по-разному в зависимости от вашей машины, но байт-код выглядит одинаково на всех машинах. Можно сказать, что машинный код оптимизирован для вашей установки.
Возвращаясь к реализации CPython, процесс цепочки инструментов выглядит следующим образом:
- CPython компилирует ваш исходный код Python в байт-код.
- Затем этот байт-код выполняется на виртуальной машине CPython.
Альтернативные виртуальные машины: Jython, IronPython и др.
Как я упоминал ранее, Python имеет несколько реализаций. Опять же, как упоминалось ранее, наиболее распространенным является CPython, но есть и другие, которые следует упомянуть в этом руководстве по сравнению. Это реализация Python, написанная на C и считающаяся реализацией «по умолчанию».
А как насчет альтернативных реализаций Python? Одним из наиболее известных является Jython, реализация Python, написанная на Java, которая использует JVM. В то время как CPython создает байт-код для запуска на виртуальной машине CPython, Jython создает байт- код Java для запуска на JVM (это то же самое, что создается при компиляции программы на Java).
«Зачем вам использовать альтернативную реализацию?» — спросите вы. Ну, во-первых, эти разные реализации Python отлично сочетаются с разными технологическими стеками .
CPython позволяет очень легко писать C-расширения для вашего кода Python, потому что в конце концов он выполняется интерпретатором C. Jython, с другой стороны, очень упрощает работу с другими Java-программами: вы можете без дополнительных усилий импортировать любые Java-классы, вызывая и используя свои Java-классы из своих Jython-программ. (Кроме того: если вы не думали об этом внимательно, это на самом деле безумие. Мы находимся в той точке, где вы можете смешивать и смешивать разные языки и компилировать их все в одну и ту же субстанцию. (Как упоминал Ростин, программы, которые смесь Фортрана и кода C существует уже давно. Так что, конечно, это не обязательно ново. Но все равно круто.))
Например, это допустимый код Jython:
[Java HotSpot(TM) 64-Bit Server VM (Apple Inc.)] on java1.6.0_51 >>> from java.util import HashSet >>> s = HashSet(5) >>> s.add("Foo") >>> s.add("Bar") >>> s [Foo, Bar]
IronPython — еще одна популярная реализация Python, полностью написанная на C# и ориентированная на стек .NET. В частности, он работает на так называемой виртуальной машине .NET, общеязыковой среде выполнения Microsoft (CLR), сравнимой с JVM.
Можно сказать, что Jython: Java :: IronPython: C# . Они работают на одних и тех же соответствующих виртуальных машинах, вы можете импортировать классы C# из кода IronPython, классы Java из кода Jython и т. д.
Вполне возможно выжить, даже не касаясь реализации Python, отличной от CPython. Но у переключения есть свои преимущества, большинство из которых зависит от вашего стека технологий. Используете много языков на основе JVM? Jython может быть для вас. Все о стеке .NET? Возможно, вам стоит попробовать IronPython (и, возможно, вы уже это сделали).
Между прочим: хотя это не будет причиной для использования другой реализации, обратите внимание, что эти реализации действительно отличаются по поведению, помимо того, как они обрабатывают ваш исходный код Python. Однако эти различия, как правило, незначительны и исчезают или появляются со временем, поскольку эти реализации находятся в стадии активной разработки. Например, IronPython по умолчанию использует строки Unicode; Однако CPython по умолчанию использует ASCII для версий 2.x (с ошибкой UnicodeEncodeError для символов, отличных от ASCII), но по умолчанию поддерживает строки Unicode для 3.x.
Своевременная компиляция: PyPy и будущее
Итак, у нас есть реализация Python, написанная на C, одна на Java и одна на C#. Следующий логический шаг: реализация Python, написанная на… Python. (Образованный читатель заметит, что это немного вводит в заблуждение.)
Здесь все может запутаться. Во-первых, давайте обсудим JIT-компиляцию.
JIT: почему и как
Напомним, что собственный машинный код намного быстрее, чем байт-код. А что, если бы мы могли скомпилировать часть нашего байт-кода, а затем запустить его как нативный код? Нам пришлось бы заплатить некоторую цену за компиляцию байт-кода (то есть время), но если бы конечный результат был быстрее, это было бы здорово! В этом причина JIT-компиляции — гибридного метода, сочетающего в себе преимущества интерпретаторов и компиляторов. Проще говоря, JIT хочет использовать компиляцию для ускорения интерпретируемой системы.
Например, общий подход, используемый JIT:

- Определите байт-код, который выполняется часто.
- Скомпилируйте его в собственный машинный код.
- Кэшировать результат.
- Всякий раз, когда для запуска задан один и тот же байт-код, вместо этого используйте предварительно скомпилированный машинный код и пожинайте плоды (т. е. повышение скорости).
В этом и заключается реализация PyPy: внедрение JIT в Python (предыдущие усилия см. в Приложении ). Есть, конечно, и другие цели: PyPy стремится быть кроссплатформенным, не требующим большого объема памяти и поддерживающим работу без стека. Но JIT действительно является его преимуществом. Говорят, что в среднем по нескольким временным тестам производительность повышается в 6,27 раза. Разбивку см. на этой диаграмме из Центра скорости PyPy:
PyPy трудно понять
У PyPy огромный потенциал, и на данный момент он хорошо совместим с CPython (поэтому он может запускать Flask, Django и т. д.).
Но вокруг PyPy много путаницы (см., например, это бессмысленное предложение создать PyPyPy…). На мой взгляд, это в первую очередь потому, что PyPy на самом деле состоит из двух вещей:
Интерпретатор Python, написанный на RPython (не на Python (я солгал раньше)). RPython — это подмножество Python со статической типизацией. В Python «практически невозможно» строго рассуждать о типах (почему это так сложно? Рассмотрим тот факт, что:
x = random.choice([1, "foo"])
будет действительным кодом Python (кредит Адеману). Каков тип
x
? Как мы можем рассуждать о типах переменных, если типы даже не соблюдаются строго?). С RPython вы жертвуете некоторой гибкостью, но вместо этого гораздо проще рассуждать об управлении памятью и многом другом, что позволяет проводить оптимизацию.Компилятор, который компилирует код RPython для различных целей и добавляет в JIT. Платформа по умолчанию — C, т. е. компилятор RPython-to-C, но вы также можете ориентироваться на JVM и другие.
Исключительно для ясности в этом руководстве по сравнению Python я буду называть их PyPy (1) и PyPy (2).
Зачем вам эти две вещи и почему под одной крышей? Подумайте об этом так: PyPy (1) — это интерпретатор, написанный на RPython. Таким образом, он принимает пользовательский код Python и компилирует его в байт-код. Но сам интерпретатор (написанный на RPython) должен быть интерпретирован другой реализацией Python для запуска, верно?
Ну, мы могли бы просто использовать CPython для запуска интерпретатора. Но это будет не очень быстро.
Вместо этого идея состоит в том, что мы используем PyPy (2) (называемый RPython Toolchain) для компиляции интерпретатора PyPy в код для другой платформы (например, C, JVM или CLI) для запуска на нашей машине, добавляя в JIT как хорошо. Это волшебно: PyPy динамически добавляет JIT к интерпретатору, создавая собственный компилятор! ( Опять же, это сумасшествие: мы компилируем интерпретатор, добавляя еще один отдельный автономный компилятор. )
В итоге получается автономный исполняемый файл, который интерпретирует исходный код Python и использует JIT-оптимизацию. Как раз то, что мы хотели! Это немного, но, возможно, эта диаграмма поможет:
Повторим еще раз: настоящая прелесть PyPy в том, что мы можем сами написать кучу разных интерпретаторов Python в RPython, не беспокоясь о JIT. Затем PyPy реализует для нас JIT с помощью RPython Toolchain/PyPy (2).
На самом деле, если мы углубимся еще больше, вы теоретически можете написать интерпретатор для любого языка, скормить его PyPy и получить JIT для этого языка. Это связано с тем, что PyPy фокусируется на оптимизации фактического интерпретатора, а не на деталях языка, который он интерпретирует.
В качестве небольшого отступления отмечу, что JIT сама по себе совершенно увлекательна. Он использует технику, называемую трассировкой, которая выполняется следующим образом:
- Запустите интерпретатор и интерпретируйте все (без добавления JIT).
- Сделайте небольшое профилирование интерпретируемого кода.
- Определите операции, которые вы выполняли ранее.
- Скомпилируйте эти биты кода в машинный код.
Более того, эта статья очень доступна и очень интересна.
Подводя итог: мы используем компилятор PyPy RPython-to-C (или другой целевой платформы) для компиляции интерпретатора PyPy, реализованного на RPython.
Подведение итогов
После длительного сравнения реализаций Python я должен спросить себя: почему это так здорово? Почему стоит реализовать эту безумную идею? Я думаю, что Алекс Гейнор хорошо выразил это в своем блоге: «[PyPy — это будущее], потому что [он] предлагает более высокую скорость, большую гибкость и лучшую платформу для развития Python».
Вкратце:
- Это быстро, потому что он компилирует исходный код в собственный код (используя JIT).
- Он гибкий, потому что добавляет JIT к вашему интерпретатору с очень небольшой дополнительной работой.
- Он гибкий (опять же), потому что вы можете писать свои интерпретаторы на RPython , который легче расширять, чем, скажем, C (на самом деле, это настолько просто, что есть учебник по написанию ваших собственных интерпретаторов).
Приложение. Другие имена Python, которые вы, возможно, слышали
Python 3000 (Py3k): альтернативное название для Python 3.0, основного, обратно несовместимого выпуска Python, который вышел на сцену в 2008 году. Команда Py3k предсказала, что для полного принятия этой новой версии потребуется около пяти лет. И хотя большинство (предупреждение: анекдотическое утверждение) разработчиков Python продолжают использовать Python 2.x, люди все больше осознают Py3k.
- Cython: надмножество Python, включающее привязки для вызова функций C.
- Цель: позволить вам писать расширения C для вашего кода Python.
- Также позволяет добавлять статическую типизацию к существующему коду Python, позволяя компилировать его и достигать производительности, подобной C.
- Это похоже на PyPy, но не то же самое. В этом случае вы принудительно вводите код пользователя перед передачей его компилятору. С PyPy вы пишете старый добрый Python, а компилятор обрабатывает любые оптимизации.
Numba: «специализирующий компилятор точно в срок», добавляющий JIT к аннотированному коду Python. Проще говоря, вы даете ему несколько подсказок, и это ускоряет часть вашего кода. Numba входит в состав дистрибутива Anaconda, набора пакетов для анализа данных и управления ими.
IPython: сильно отличается от всего обсуждаемого. Вычислительная среда для Python. Интерактивный с поддержкой наборов инструментов GUI и браузера и т. д.
- Psyco: модуль расширения Python и одна из первых попыток Python JIT. Однако с тех пор он был помечен как «необслуживаемый и мертвый». Фактически, ведущий разработчик Psyco Армин Риго сейчас работает над PyPy.
Привязки к языку Python
RubyPython: мост между виртуальными машинами Ruby и Python. Позволяет встраивать код Python в код Ruby. Вы определяете, где Python запускается и где останавливается, а RubyPython распределяет данные между виртуальными машинами.
PyObjc: языковые привязки между Python и Objective-C, действующие как мост между ними. На практике это означает, что вы можете использовать библиотеки Objective-C (включая все необходимое для создания приложений OS X) из кода Python и модули Python из кода Objective-C. В этом случае удобно, что CPython написан на C, который является подмножеством Objective-C.
PyQt: в то время как PyObjc дает вам привязку для компонентов графического интерфейса OS X, PyQt делает то же самое для среды приложений Qt, позволяя вам создавать богатые графические интерфейсы, получать доступ к базам данных SQL и т. д. Еще один инструмент, направленный на то, чтобы сделать Python проще для других фреймворков.
JavaScript-фреймворки
pyjs (Pyjamas): платформа для создания веб-приложений и настольных приложений на Python. Включает компилятор Python-to-JavaScript, набор виджетов и некоторые другие инструменты.
Brython: виртуальная машина Python, написанная на JavaScript, позволяющая выполнять код Py3k в браузере.