Версии Python и magic-число: почему pyc не совместим
Каждая минорная версия CPython получает новое magic-число, и pyc одной версии не грузится другой. Находим число в importlib, сверяем с заголовком pyc и проверяем версию кодом.
Редакция Питоники
Файл pyc из второго урока начинается с magic-числа — но зачем интерпретатору паспорт версии на каждом файле кэша? Ответ самый практический: байткод — не стандарт языка, а внутренний формат конкретного CPython. Каждая минорная версия меняет набор инструкций, их аргументы и формат констант. Загрузив файл чужой версии, интерпретатор собрал бы кодовый объект из байткода, который не понимает. Magic-число и есть барьер. Каждая минорная версия Python получает новое magic-число, и файл, собранный одной версией, другой вслепую не загрузится.
Число своей версии
Число работающего интерпретатора доступно как importlib.util.MAGIC_NUMBER — те самые четыре байта, что стоят в начале каждого своего pyc:
import importlib.util
magic = importlib.util.MAGIC_NUMBER
print('тип магического числа:', type(magic).__name__)
print('длина:', len(magic), 'байта')
print('хвост перевода строки на месте:', magic[2:] == b'\r\n')
тип магического числа: bytes длина: 4 байта хвост перевода строки на месте: True
Число — это bytes длиной 4: два первых байта кодируют номер версии и меняются от релиза к релизу, два последних — 0d 0a, перевод строки, обязательный хвост формата. Мы не печатаем сами байты: они зависят от версии, на которой вы читаете урок, — а вот длина и хвост одинаковы везде.
| Версия Python | Magic-число | Первые байты pyc |
|---|---|---|
| 3.9 | 3425 | 61 0d 0d 0a |
| 3.10 | 3439 | 6f 0d 0d 0a |
| 3.11 | 3495 | a7 0d 0d 0a |
| 3.12 | 3531 | cb 0d 0d 0a |
| 3.13 | 3571 | f3 0d 0d 0a |
Таблица объясняет правило совместимости одной строкой: каждая минорная версия — новое число. Микро-релизы (3.12.1, 3.12.7) число не меняют — между ними pyc совместимы. Число — это младшие два байта в little-endian: 3531 — это cb 0d, 3571 — f3 0d.
Сверка с заголовком pyc
Интерпретатор при загрузке кэша делает ровно одну проверку: первые четыре байта файла должны совпасть с его собственным MAGIC_NUMBER. Повторим её руками:
import glob
import importlib.util
import os
import py_compile
with open('magic_demo.py', 'w', encoding='utf-8') as f:
f.write('N = 1\n')
pyc = py_compile.compile('magic_demo.py')
with open(pyc, 'rb') as f:
head = f.read(4)
print('заголовок pyc совпал с магическим числом:', head == importlib.util.MAGIC_NUMBER)
for p in glob.glob('__pycache__/*.pyc'):
os.remove(p)
if os.path.isdir('__pycache__'):
os.rmdir('__pycache__')
os.remove('magic_demo.py')
заголовок pyc совпал с магическим числом: True
Совпадение гарантировано — файл собран этим же интерпретатором. А теперь обратная ситуация: окажись в первых байтах число версии 3.11, проверка вернула бы False, и загрузчик отказался бы от файла. Что именно говорит интерпретатор при отказе, видно на реальном запуске:
RuntimeError: Bad magic number in .pyc file
Это сообщение встречается в двух житейских ситуациях: pyc скопировали между машинами с разными версиями Python, или pyc заехали в проект от другого интерпретатора через общую папку. Лечение всегда одно: удалить чужой кэш и дать своей версии собрать свежий — исходник-то на месте.
Проверка версии из кода
Версия программы читается из sys.version_info — именованного кортежа с полями major, minor, micro. Печатать можно только устойчивые поля: major у современного Python всегда 3, а minor и micro зависят от машины, на которой выполняется код:
import sys
v = sys.version_info
print('мажорная версия:', v.major)
print('микро-версия больше нуля:', v.micro > 0)
print('это третья версия Python:', v.major == 3)
мажорная версия: 3 микро-версия больше нуля: True это третья версия Python: True
На вашей машине строки с True напечатаются одинаково, что бы ни стояло в minor: в песочнице урока 3.12, у вас, возможно, 3.13 или новее. Сравнение версий — типичный приём совместимости: код спрашивает не «какая у тебя версия», а «подойдёт ли мне такая»:
import sys
def pyc_will_load(pyc_minor, current_minor):
return pyc_minor == current_minor
mine = sys.version_info.minor
print('pyc этой же версии загрузится:', pyc_will_load(mine, mine))
print('pyc соседней версии загрузится:', pyc_will_load(mine + 1, mine))
print('pyc старой версии загрузится:', pyc_will_load(mine - 1, mine))
pyc этой же версии загрузится: True pyc соседней версии загрузится: False pyc старой версии загрузится: False
Функция сравнивает минорные версии между собой, и вывод не зависит от того, какая именно у вас: pyc строго одной версии True, любые соседи False. Никакой «обратной совместимости кэша» не существует — 3.13 не грузит pyc даже собственной 3.12.
Метка версии в имени файла
Имя pyc-файла в __pycache__ собирается из имени модуля и метки реализации: у CPython 3.12 это cpython-312. Метка доступна как sys.implementation.cache_tag — и по ней имя файла предсказывается без чтения каталога:
import glob
import os
import py_compile
import sys
with open('named_demo.py', 'w', encoding='utf-8') as f:
f.write('N = 1\n')
pyc = py_compile.compile('named_demo.py')
name = os.path.basename(pyc)
expected = 'named_demo.' + sys.implementation.cache_tag + '.pyc'
print('имя pyc построено по метке версии:', name == expected)
print('имя начинается с имени модуля:', name.startswith('named_demo.'))
print('имя заканчивается на .pyc:', name.endswith('.pyc'))
for p in glob.glob('__pycache__/*.pyc'):
os.remove(p)
if os.path.isdir('__pycache__'):
os.rmdir('__pycache__')
os.remove('named_demo.py')
имя pyc построено по метке версии: True имя начинается с имени модуля: True имя заканчивается на .pyc: True
Мы не печатали само имя — оно содержит метку вашей версии, — но три проверки доказывают правило: модуль. + cache_tag + .pyc. Метка в имени означает, что 3.12 и 3.13 могут держать кэши рядом, не затирая друг друга: у каждой версии свой файл.
Совместимость и практика
Из правила magic-числа следуют привычки, экономящие часы: виртуальное окружение создают под одну версию и не шарят между 3.12 и 3.13; каталог __pycache__ не переносят между машинами; при обновлении Python кэш собирается заново автоматически — удалять его руками не нужно, интерпретатор сам увидит чужое число и пересоберёт.
import importlib.util
def can_load(pyc_magic):
return pyc_magic == importlib.util.MAGIC_NUMBER
print('pyc своей версии пройдёт проверку:', can_load(importlib.util.MAGIC_NUMBER))
print('случайные четыре байта пройдут проверку:', can_load(b'\x00\x00\x00\x00'))
pyc своей версии пройдёт проверку: True случайные четыре байта пройдут проверку: False
python --version
Python 3.13.15
- magic-число — 4 байта в начале pyc: паспорт версии, меняется в каждом минорном релизе;
- своё число доступно как importlib.util.MAGIC_NUMBER, чужой файл даёт Bad magic number;
- версия кода читается из sys.version_info: major стабильна, minor и micro зависят от машины;
- имя pyc строится из метки sys.implementation.cache_tag — у каждой версии свой кэш;
- micro-релизы (3.12.1 и 3.12.7) число не меняют: их кэши совместимы между собой.
Magic-число — только первый рубеж проверки кэша. Второй — время и размер исходника в заголовке pyc: по ним интерпретатор решает, свеж ли кэш даже для своей версии. Вся механика — в уроке про __pycache__.
Сначала предскажи ответ в голове — это главный навык программиста.
import importlib.util
magic = importlib.util.MAGIC_NUMBER
print(type(magic).__name__, len(magic))
import sys
minor = sys.version_info.minor
print(minor == minor)
print(minor == minor + 1)
import importlib.util
print(len(importlib.util.MAGIC_NUMBER[2:]) == 2)
print(importlib.util.MAGIC_NUMBER[2:] == b'\r\n')
1. Почему pyc от Python 3.12 не загружается интерпретатором 3.11?
2. Совместимы ли pyc между 3.12.1 и 3.12.7?
3. Где код может узнать версию работающего интерпретатора?
4. Что говорит интерпретатор при попытке загрузить pyc с чужим magic-числом?
5. Поможет ли переписать magic-число в чужом pyc под свою версию?
6. Из чего собирается имя pyc-файла в __pycache__?
7. Что напечатает сравнение sys.version_info.minor == 12 на машине с Python 3.13?
Напишите проверку загрузчика: функция can_load принимает четыре байта заголовка и отвечает, совпали ли они с магическим числом текущего интерпретатора. Проверьте её на своём magic и на четырёх нулевых байтах.
Почему pyc от одной версии Python не работает на другой?
Каждая минорная версия меняет байткод и получает новое magic-число: у 3.12 это 3531, у 3.13 — 3571. Загрузчик сверяет первые 4 байта файла со своим importlib.util.MAGIC_NUMBER, и чужой файл отбрасывается — кэш пересобирается из исходника.
Урок 2: устройство pyc-файлаКакая версия Python у меня установлена и как это проверить из кода?
В коде — sys.version_info с полями major, minor, micro; в консоли — python --version. Для проверок совместимости сравнивайте version_info.minor: например, код с match-инструкциями требует minor >= 10.
Что означает ошибка Bad magic number in .pyc file?
Кто-то пытается запустить pyc, собранный другой версией Python: числа в заголовке не совпали. Удалите чужой кэш (каталог __pycache__) и дайте своей версии собрать свежий — исходники при этом не страдают.
Можно ли вручную исправить magic-число в pyc?
Бессмысленно: заголовок совпадёт, но тело — marshal-блок с байткодом чужой версии — останется несовместимым. Единственный честный путь — перекомпилировать исходник нужной версией: py_compile или обычный импорт сделают это сами.
Урок 3: py_compile и compileallМеняется ли magic-число в обновлениях вроде 3.12.1 → 3.12.7?
Нет: микро-релизы внутри одной минорной версии делят одно число и общий кэш. Границы совместимости проходят по минорным версиям — 3.12 и 3.13 всегда держат отдельные pyc.
Понравился урок? Сошлитесь на него
«Каждая минорная версия Python получает новое magic-число, и файл, собранный одной версией, другой вслепую не загрузится.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Компиляция · Урок 2
pyc-файл: устройство скомпилированного файла Python
pyc — это кодовый объект, упакованный в байты: 16 байт заголовка с magic-числом, временем и размером исходника, дальше marshal-блок с байткодом. Разбираем файл до последнего байта.
Компиляция · Урок 4
dis: дизассемблер байткода Python
dis печатает байткод функции инструкция за инструкцией: какая команда, над какими значениями, куда прыгает условие. Учимся читать колонки вывода и понимать стековую машину.
Компиляция · Урок 6
__pycache__: кэш компиляции Python
Каталог __pycache__ появляется сам и живёт по простым правилам: pyc свеж, пока у исходника прежние время и размер. Разбираем инвалидацию, очистку и отключение кэша.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
openpyxl · Урок 8
Числовые форматы в openpyxl: рубли, проценты и разделители
Строка number_format превращает голое 1234.5 в «1 234.50 ₽»: задаём рубли, проценты и разделители тысяч — и разбираемся, почему значение ячейки при этом не меняется.
openpyxl number format рубли
openpyxl · Урок 20
Проект: автоматический прайс-лист с форматированием и диаграммой
Финальный проект курса: прайс-лист «Дуката» целиком из кода — данные, шапка, форматы, заливка, фильтр и диаграмма выручки — плюс чек-лист продукта и итоги всех двадцати уроков.
openpyxl number format рубли
Компиляция · Урок 17
PyPy: JIT-компилятор Python и когда он выигрывает
Тот же язык, другой движок: PyPy компилирует горячие участки прямо во время работы. Разбираем разогрев, ускорение в разы, совместимость с C-расширениями и случаи, когда CPython остаётся правильным выбором.
pypy или cpythonpypy совместимость