Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 16 из 20 Средний 15 мин 100 XP

Реализации 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 переносится на любую реализацию, а вот конкретные цифры версии в вывод печать не выносит — они у каждой среды свои, и вывод с ними не был бы переносимым.

РеализацияНаписана наСредаЧем берёт
CPythonCВезде: десктоп, сервер, WASMЭталон: весь стек совместимости, все пакеты
PyPyPython (RPython), JITДесктоп и серверыСкорость чистого Python в разы
MicroPythonCМикроконтроллерыИнтерпретатор в сотни килобайт
JythonJavaJVMПрямой доступ к Java-классам
IronPythonC#.NETВстраивание скриптов в .NET-приложения
RustPythonRustВстраивание, WASMОткрытый эксперимент, embed в Rust-проекты
BrythonJavaScriptБраузер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, а альтернативы догоняют с задержкой в один-два релиза. Поэтому переносимый код проверяет не имя среды, а доступность возможности:

match — часть языка, проверяемая сравнением версии
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:

getrefcount — CPython-специфика
import sys
value = 'тест'
print('ссылок у строки:', sys.getrefcount(value))
Пример не запускается как переносимый: sys.getrefcount существует только у CPython-семейства, на PyPy его нет вовсе, а число ссылок зависит от деталей реализации и потому в уроке не печатается. В песочнице Pyodide этот вызов сработал бы — это тоже CPython.

Отсюда практическое правило: освобождение ресурсов доверяйте with, а не моменту удаления объекта. with гарантирован языком и работает одинаково на любой реализации; del-магия — соглашение одной.

Второй переносимый опознавательный признак — cache_tag из урока 5: метка, из которой CPython собирает имена pyc-файлов. Она есть у реализаций с байткод-кэшем и начинается с имени реализации:

cache_tag начинается с имени реализации
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()))
Проверь себя
0 / 7

1. Что такое CPython по отношению к языку Python?

2. Как код узнаёт, какая реализация его исполняет?

3. Что напечатает sys.implementation.name в песочнице этого сайта?

4. Какое поведение гарантировано ЛЮБОЙ реализации Python?

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

6. Что делает cache_tag у CPython?

7. Где уместно ветвление по sys.implementation.name?

Карточки терминов
Запомнено: 0 / 7
Практика

Напишите функцию environment(), которая возвращает пару: имя реализации и проверку, что это эталон. Напечатайте обе величины.

practice.py
Вопросы и ответы по уроку

Что такое 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-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

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