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

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

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

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

Версия PythonMagic-числоПервые байты pyc
3.9342561 0d 0d 0a
3.1034396f 0d 0d 0a
3.113495a7 0d 0d 0a
3.123531cb 0d 0d 0a
3.133571f3 0d 0d 0a

Таблица объясняет правило совместимости одной строкой: каждая минорная версия — новое число. Микро-релизы (3.12.1, 3.12.7) число не меняют — между ними pyc совместимы. Число — это младшие два байта в little-endian: 3531 — это cb 0d, 3571 — f3 0d.

Сверка с заголовком pyc

Интерпретатор при загрузке кэша делает ровно одну проверку: первые четыре байта файла должны совпасть с его собственным MAGIC_NUMBER. Повторим её руками:

сверка magic из заголовка pyc
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, и загрузчик отказался бы от файла. Что именно говорит интерпретатор при отказе, видно на реальном запуске:

запуск pyc чужой версии
RuntimeError: Bad magic number in .pyc file
Сообщение снято при запуске pyc с подменённым magic-числом на CPython 3.13; формулировка может отличаться между версиями. В песочнице командная строка недоступна.

Это сообщение встречается в двух житейских ситуациях: pyc скопировали между машинами с разными версиями Python, или pyc заехали в проект от другого интерпретатора через общую папку. Лечение всегда одно: удалить чужой кэш и дать своей версии собрать свежий — исходник-то на месте.

Проверка версии из кода

Версия программы читается из sys.version_info — именованного кортежа с полями major, minor, micro. Печатать можно только устойчивые поля: major у современного Python всегда 3, а minor и micro зависят от машины, на которой выполняется код:

версия интерпретатора из sys.version_info
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 или новее. Сравнение версий — типичный приём совместимости: код спрашивает не «какая у тебя версия», а «подойдёт ли мне такая»:

проверка совместимости pyc по версии
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 — и по ней имя файла предсказывается без чтения каталога:

имя pyc предсказывается по метке версии
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
Командная строка недоступна в песочнице, и вывод зависит от машины: показана локальная установка 3.13.15, у вас номер будет своим, а в песочнице урока работает 3.12. Это то же число, что в sys.version_info и зашито в magic-числе.
  • 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')
Проверь себя
0 / 7

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?

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

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

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

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

TelegramVK

Что дальше

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

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

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