БэкендPythonДругой язык

Интерпретатор Python в 1024 байта: из чего на самом деле состоит язык

Разбираем ручной эксперимент Остина Хенли: как в 1024 байта C-кода поместились разбор и выполнение Python-подобного подмножества и что пришлось убрать.

Кодик

Автор

6 мин чтения

Интерпретатор Python в 1024 байта показывает не размер Python, а минимальный набор решений, после которого текст уже кажется знакомым языком. Остин Хенли вручную написал на C крошечное подмножество с отступами, условиями, циклами и функциями. Эксперимент отлично объясняет устройство интерпретатора именно потому, что почти всё привычное пришлось выбросить.

6 сентября 2026 года Хенли опубликовал исходник и рассказ о код-гольфе. Первая цель в 512 байт оказалась слишком жёсткой: простой калькулятор уже занимал лимит, но ещё не выглядел как Python. Тогда автор поднял границу до 1024 байт, сначала собрал читаемую версию объёмом более 4800 байт, а потом сокращал имена, проверки и промежуточные действия. Итог умеет запустить ограниченный FizzBuzz, однако не претендует на совместимость с обычными Python-программами.

1Чтение символов

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

2Рекурсивный спуск

Небольшие функции разбирают числа, операции, сравнения и блоки.

3Выполнение сразу

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

Программист вручную сокращает код маленького интерпретатора Python
Код-гольф делает архитектуру видимой: каждый сохранённый байт оплачивается ограничением или предположением.

Интерпретатор Python в 1024 байта: три слоя без лишнего

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

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

символы исходника
        ↓
разбор выражения по приоритету
        ↓
немедленное изменение таблицы переменных
        ↓
переход к следующей строке

Циклы раскрывают цену прямого выполнения. После тела while или for интерпретатор возвращает позицию назад и заново разбирает условие и блок. Функции используют похожий трюк: таблица запоминает позицию определения, вызов временно прыгает к телу, а затем возвращает позицию вызывающего кода. Вложенные блоки опираются на стек вызовов самого C.

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

Конфигуратор бюджета: что оставить в мини-языке

Выберите ограничение эксперимента

Это учебная модель компромиссов. Полосы не равны реальной стоимости функций в байтах: точные размеры зависят от кода, компилятора и правил соревнования.

По опыту автора лимит слишком тесный для выбранной цели.
  • Можно оставить арифметическое ядро
  • Python-похожесть быстро теряется
  • Условия и блоки конкурируют за место
  • От идеи 512 байт автор отказался
Помещается узнаваемое, но очень хрупкое подмножество.
  • Целые числа и присваивание
  • Арифметика с приоритетом
  • if, else, while, for
  • Функции без аргументов
  • Однобуквенные имена
  • Нет обработки ошибок
Приоритет меняется с размера на ясность.
  • Токены с координатами
  • Понятное дерево разбора
  • Диагностика неверного ввода
  • Тесты каждого слоя
  • Нормальные имена и структуры
  • Документированная грамматика

Что именно поместилось, а что исчезло

По списку автора версия на 1024 байта поддерживает целочисленные литералы и переменные, присваивание, операции +, -, * и %, одно сравнение в выражении, целочисленную истинность, условия, циклы, функции без аргументов, рекурсивные вызовы, блоки по отступам, простой print и комментарии. Некоторые детали намеренно не совпадают с Python. Например, области видимости отсутствуют, унарные знаки ограничены, а печать принимает лишь один строковый литерал или целочисленное выражение.

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

Путь программы через чтение символов, парсер и прямое выполнение
Мини-интерпретатор соединяет разбор и выполнение. Полноценная среда выполнения разделяет больше этапов и обязана обрабатывать намного больше случаев.

Почему это не «весь Python»

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

CPython, основная реализация языка, идёт более длинным путём. В объяснении Хенли он токенизирует текст, строит AST, выполняет анализ и оптимизации, создаёт байткод и затем исполняет его. Репозиторий CPython также содержит стандартную библиотеку, тесты, инструменты сборки, платформенный код и документацию. Сравнивать их объём с одним гольф-файлом бессмысленно: они решают разные задачи и дают разные гарантии.

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

Как повторить идею без код-гольфа

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

Следующий шаг состоит не в сокращении имён, а в разделении ответственности. Лексер возвращает токены, парсер строит небольшие узлы, интерпретатор обходит их и хранит окружение. Так можно распечатать дерево для строки x = 1 + 2 * 3 и увидеть, почему умножение выполняется раньше. Когда эта версия покрыта тестами, полезно отдельно попробовать прямое выполнение и сравнить сложность циклов.

Это настоящий Python?

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

Зачем заново разбирать цикл?

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

Почему нельзя использовать такой код в приложении?

Он оптимизирован под размер и корректный демонстрационный ввод. В нём нет нужной диагностики, совместимости и защитных проверок.

Подробности эксперимента опубликованы в блоге Остина Хенли. Для точной семантики сверяйтесь со справочником языка, а устройство основной реализации исследуйте в репозитории CPython.

Продолжить разбор языка

Синтаксис и функции можно закрепить на курсе Python. Затем сравните путь от старого языка в статье от Basic к Python, проверьте базовые понятия по вопросам Python junior и потренируйте декомпозицию на алгоритмических задачах. Попробуйте описать свой мини-язык пятью правилами до написания первой функции.