Реализации Python: CPython и альтернативные среды
Python — это язык, а CPython — его главная реализация. Разбираем, кто ещё исполняет Python-код, как узнать свою среду через sys.implementation и что гарантировано любому коду на любой реализации.
Редакция Питоники
До этого раздела мы говорили о Python как о чём-то одном: байткод из урока 1, pyc из урока 2, magic-число из урока 5 — всё это на самом деле устройство конкретной программы, которая называется CPython. Но Python — это язык с правилами, а исполняющих его программ несколько. Этот урок раскладывает вещи по именам: что принадлежит языку, что — реализации, и как код узнаёт, в какой среде он запущен.
CPython: эталон, с которого всё считывается
CPython — реализация на C, которую разрабатывает сама команда языка python.org. Когда в языке появляется новая фича, сначала она попадает в CPython, и только потом — в остальные. Все pyc-файлы, __pycache__, marshal и dis из предыдущих уроков — детали CPython: другие реализации используют свои форматы или вовсе работают без файлов байткода. Проверить, кто исполняет ваш код, можно одним вызовом:
import sys
print('реализация:', sys.implementation.name)
print('это CPython?', sys.implementation.name == 'cpython')
реализация: cpython это CPython? True
Песочница на странице и ваш локальный Python ответят одинаково: cpython. На настоящем PyPy этот же код напечатал бы pypy, на Jython — java, на IronPython — cli. sys.implementation — переносимый способ задать вопрос: он есть у всех современных реализаций именно для того, чтобы код мог отличать среды без хаков под платформу.
Как код узнаёт свою среду
Ветвление по имени реализации — стандартный приём там, где альтернативы действительно различаются: разные пути ускорения, разные границы возможностей. Каркас всегда одинаковый:
import sys
impl = sys.implementation.name
if impl == 'cpython':
label = 'эталонная реализация на C'
elif impl == 'pypy':
label = 'реализация с JIT'
else:
label = 'альтернативная среда'
print(impl, '->', label)
print('версия языка не ниже 3.10:', sys.version_info >= (3, 10))
cpython -> эталонная реализация на C версия языка не ниже 3.10: True
В песочнице сработала первая ветка; на PyPy тот же код пошёл бы во вторую и напечатал pypy -> реализация с JIT. Обратите внимание на вторую строку: проверка версии языка через сравнение version_info переносится на любую реализацию, а вот конкретные цифры версии в вывод печать не выносит — они у каждой среды свои, и вывод с ними не был бы переносимым.
| Реализация | Написана на | Среда | Чем берёт |
|---|---|---|---|
| CPython | C | Везде: десктоп, сервер, WASM | Эталон: весь стек совместимости, все пакеты |
| PyPy | Python (RPython), JIT | Десктоп и серверы | Скорость чистого Python в разы |
| MicroPython | C | Микроконтроллеры | Интерпретатор в сотни килобайт |
| Jython | Java | JVM | Прямой доступ к Java-классам |
| IronPython | C# | .NET | Встраивание скриптов в .NET-приложения |
| RustPython | Rust | Встраивание, WASM | Открытый эксперимент, embed в Rust-проекты |
| Brython | JavaScript | Браузер | Python-синтаксис над DOM |
Судьбы у реализаций разные. Jython застрял на уровне Python 2 — линия Python 3 двигается тяжело. IronPython дошёл до современной тройки, но совместимость отстаёт от CPython на релизы. RustPython — молодой проект: часть языка работает, часть ещё в пути. PyPy и MicroPython — самые живые: первый выигрывает скоростью, второй — размером, и каждому из них дальше по отдельному уроку: PyPy — урок 17, MicroPython — урок 18, а урок 19 разберёт Jython, IronPython, RustPython и Brython вместе.
Язык один — поведение гарантировано
Что именно принадлежит языку, а не реализации? Правила: как ведут себя типы, словари, сортировка, строки. Любая реализация, называющая себя Python, обязана давать на таких операциях одинаковые результаты — иначе она не имеет права так называться:
items = {'b': 2, 'a': 1}
print(sorted(items.items()))
print('-'.join(['python', 'работает', 'везде']))
print('словарь помнит порядок вставки:', list(items) == ['b', 'a'])
[('a', 1), ('b', 2)]
python-работает-везде
словарь помнит порядок вставки: TrueПара из ключа и значения сортируется одинаково на любой реализации, join склеивает как всюду, а порядок вставки словаря — часть спецификации языка, а не удача CPython. Такой код — чистый Python на встроенных типах — переносится между средами без единой правки. Различия начинаются там, где код касается края среды: скорость, память, C-расширения, платформенные библиотеки.
На чистом языке стоит писать и алгоритмы — они переносимы автоматически:
def word_stats(text):
words = text.split()
return len(words), max(words, key=len)
words, longest = word_stats('python компилирует код в байткод')
print('слов:', words)
print('самое длинное:', longest)
слов: 5 самое длинное: компилирует
Новые возможности языка приходят с задержкой
Конструкция match из Python 3.10 — часть языка, и современные реализации её поддерживают. Но порядок всегда такой: фича появляется в CPython, а альтернативы догоняют с задержкой в один-два релиза. Поэтому переносимый код проверяет не имя среды, а доступность возможности:
import sys
def describe(code):
match code:
case 'pyc':
return 'байткод'
case 'pyz':
return 'архив'
case _:
return 'исходник'
print(describe('pyc'))
print(describe('exe'))
print('match доступен:', sys.version_info >= (3, 10))
байткод исходник match доступен: True
Сравнение version_info с кортежем — переносимая проверка: она не спрашивает, кто исполняет код, а спрашивает, на какой ступени языка он находится. Это надёжнее, чем угадывать реализацию: среда может быть альтернативной и при этом вполне современной.
Границы переносимости
Самая коварная CPython-деталь — подсчёт ссылок: объект удаляется в момент, когда пропадает последняя ссылка, и код может этим пользоваться — закрывать файлы и соединения прямо в del. У PyPy другая модель памяти: сборка мусора откладывает уничтожение на неопределённый срок. Метод sys.getrefcount, который считает ссылки, существует только у CPython:
import sys
value = 'тест'
print('ссылок у строки:', sys.getrefcount(value))
Отсюда практическое правило: освобождение ресурсов доверяйте with, а не моменту удаления объекта. with гарантирован языком и работает одинаково на любой реализации; del-магия — соглашение одной.
Второй переносимый опознавательный признак — cache_tag из урока 5: метка, из которой CPython собирает имена pyc-файлов. Она есть у реализаций с байткод-кэшем и начинается с имени реализации:
import sys
impl = sys.implementation.name
tag = sys.implementation.cache_tag
print('реализация:', impl)
print('cache_tag совпадает с реализацией?', tag.startswith(impl))
реализация: cpython cache_tag совпадает с реализацией? True
В песочнице ответ True: метка кэша начинается с имени реализации. Точные значения — вещь конкретной среды, поэтому печатается только проверка, а не сама метка. На PyPy проверка тоже вернула бы True: у него свой байткод-кэш и свой cache_tag с тем же устройством.
- гарантировано языком: синтаксис, встроенные типы, словари с порядком вставки, сортировка, стандартная библиотека;
- решает реализация: скорость, расход памяти, поддержка C-расширений, платформенные библиотеки;
- свой путь у каждой среды: у CPython — pyc и __pycache__, у PyPy — JIT, у MicroPython — урезанный интерпретатор;
- переносимый опознавательный признак — sys.implementation: он есть у всех современных сред именно для проверок.
Python — это язык, а CPython — его эталонная реализация: тот же код на любой совместимой среде даёт те же результаты. Различия живут на границах — в скорости, размере и платформенных мостах. Следующие три урока — об этих границах: PyPy и его JIT, MicroPython на микроконтроллерах и четыре платформенные реализации в уроке 19.
Сначала предскажи ответ в голове — это главный навык программиста.
import sys
print(sys.implementation.name)
import sys
impl = sys.implementation.name
if impl == 'cpython':
print('эталон')
else:
print('альтернатива')
items = {'b': 2, 'a': 1}
print(sorted(items.items()))
1. Что такое CPython по отношению к языку Python?
2. Как код узнаёт, какая реализация его исполняет?
3. Что напечатает sys.implementation.name в песочнице этого сайта?
4. Какое поведение гарантировано ЛЮБОЙ реализации Python?
5. Почему фичи языка появляются в альтернативных реализациях с задержкой?
6. Что делает cache_tag у CPython?
7. Где уместно ветвление по sys.implementation.name?
Напишите функцию environment(), которая возвращает пару: имя реализации и проверку, что это эталон. Напечатайте обе величины.
Что такое CPython простыми словами?
Это главная реализация языка Python, написанная на C: та самая, которую скачивают с python.org. Она компилирует код в байткод, хранит pyc и задаёт стандарт, под который равняются все остальные реализации.
Урок 1: как Python исполняет кодЧем отличаются реализации Python?
Язык и правила одинаковые — различия в среде и приоритетах: PyPy ускоряет чистый Python через JIT, MicroPython ужимается до микроконтроллеров, Jython живёт на JVM, IronPython — на .NET, Brython — в браузере. Семантика встроенных типов у всех обязана совпадать.
Урок 17: PyPyКак проверить, на какой реализации запущен код?
Через sys.implementation.name: CPython печатает cpython, PyPy — pypy, Jython — java, IronPython — cli. Для проверки возможностей языка надёжнее сравнение sys.version_info с нужным кортежем.
Что такое Pyodide?
CPython, скомпилированный в WebAssembly для исполнения в браузере. Это не отдельная реализация, а эталон в новой среде: поэтому песочница Питоники честно отвечает sys.implementation.name == 'cpython'.
Какой код переносится между реализациями без правок?
Чистый Python на встроенных типах и стандартной библиотеке: сортировка, словари, строки работают одинаково всюду. Непереносимы C-расширения и опора на детали вроде подсчёта ссылок или sys.getrefcount.
Урок 18: MicroPythonПонравился урок? Сошлитесь на него
«Python — это язык, а CPython — его эталонная реализация: тот же код на любой совместимой среде даёт те же результаты.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Компиляция · Урок 17
PyPy: JIT-компилятор Python и когда он выигрывает
Тот же язык, другой движок: PyPy компилирует горячие участки прямо во время работы. Разбираем разогрев, ускорение в разы, совместимость с C-расширениями и случаи, когда CPython остаётся правильным выбором.
Компиляция · Урок 18
MicroPython: Python для микроконтроллеров ESP32 и RP2040
Python, который живёт на чипе за пару долларов: интерпретатор в сотни килобайт, модуль machine для пинов и стандартная библиотека, ужатая до сути. Разбираем отличия от CPython и стиль кода под маленькую память.
Компиляция · Урок 1
Как Python исполняет код: байткод и виртуальная машина
Питон исполняет программу в два этапа: компилятор переводит исходник в байткод, виртуальная машина выполняет его по инструкциям. Достаём оба этапа на поверхность и вызываем компилятор сами.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Pandas · Урок 1
Что такое Pandas и как установить через pip: первые Series и DataFrame
Первая таблица DataFrame в библиотеке pandas, умный столбец Series и быстрый осмотр данных через head, info и describe — старт сквозного анализа данных интернет-магазина.
что такое dataframeчто такое pandas простыми словами
Seaborn · Урок 1
Что такое Seaborn: первый график одной строкой
Seaborn — надстройка над Matplotlib: график из пары строк, тема и подписи осей из DataFrame прилагаются. Рисуем первый lineplot и сравниваем с ручным pyplot.
seaborn matplotlib разница
Компиляция · Урок 19
Jython, IronPython, RustPython и Brython: Python на чужих платформах
Четыре реализации для четырёх сред: Python на JVM, на .NET, на Rust и прямо в браузере. Зачем их переписывали, что они умеют сегодня и чем Brython отличается от Pyodide из песочницы.
альтернативные реализации python