Файлы .pyc в Python: компиляция, декомпиляция и управление байт-кодом
Основные способы работы с .pyc файлами
Один из наиболее эффективных подходов к управлению скомпилированными файлами Python - массовая компиляция всех модулей с помощью модуля compileall. Это позволяет заранее подготовить байт-код для всего проекта, что сокращает время первого импорта и упрощает распространение приложения без исходных текстов.
Пример команды в терминале:
python -m compileall .Pip tools python (pip tools в python)
Результат выполнения - в текущей директории и всех поддиректориях появятся папки __pycache__ с файлами .pyc. Если нужна только рекурсивная компиляция, достаточно указать путь к корню проекта.
Тот же эффект достигается программно:
import compileall
compileall.compile_dir('.', force=True)
Python build tools (python build tools (инструменты сборки))
Параметр force=True перезаписывает существующие .pyc независимо от времени изменения исходного .py. Это полезно после обновления кода.
Проблема: если в проекте есть синтаксические ошибки, компиляция прерывается с исключением PyCompileError. Решение - предварительно проверять код с помощью py_compile для отдельных файлов или использовать обработку ошибок.
Как скомпилировать один .py файл в .pyc?
Для единичного файла применяется модуль py_compile. Пример:
import py_compile
py_compile.compile('script.py')
Python packaging tools (python packaging tools (инструменты сборки))
По умолчанию .pyc создаётся в папке __pycache__ с именем вида script.cpython-39.pyc. Местоположение можно изменить параметром cfile:
py_compile.compile('script.py', cfile='custom.pyc')
Python online код (онлайн редактор python)
Такой подход удобен, когда нужно подготовить только несколько ключевых модулей или протестировать компиляцию.
Ошибка: если файл содержит синтаксическую ошибку, возникает PyCompileError с описанием проблемы. Рекомендуется обрабатывать исключение, чтобы продолжать компиляцию остальных файлов:
try:
py_compile.compile('broken.py')
except py_compile.PyCompileError as e:
print(f'Ошибка компиляции: {e}')
Find python script (поиск python скрипта)
Как .pyc создаются автоматически при импорте?
Стандартное поведение интерпретатора - при первом импорте модуля Python сохраняет скомпилированный байт-код в папку __pycache__. Например, после выполнения:
import mymoduleCompiled python file (скомпилированные файлы python (.pyc))
В директории модуля появляется __pycache__/mymodule.cpython-39.pyc. Повторные импорты загружают код из .pyc, если он не старше исходного .py. Это ускоряет запуск больших приложений.
Проблема: если у пользователя нет прав на запись в директорию модуля, .pyc не создаётся, но импорт всё равно работает (интерпретатор загружает .py). Это может замедлить повторные запуски. Решение - обеспечить права на запись или использовать предварительную компиляцию через compileall во время установки.
Как просмотреть байт-код, хранящийся в .pyc?
Для анализа содержимого .pyc служит модуль dis (disassembler). Сначала нужно загрузить байт-код из файла с учётом заголовка:
import dis, importlib.util
import importlib._bootstrap_external as bootstrap
with open('__pycache__/mymodule.cpython-39.pyc', 'rb') as f:
magic = f.read(4)
flags = f.read(4)
timestamp = f.read(4)
size = f.read(4)
code = bootstrap._code_to_timestamp_pyc(f.read())
dis.dis(code[0])
где писать код на python (где писать код на python)
Этот вызов выведет список инструкций байт-кода с метками и аргументами. Для более простого способа можно использовать библиотеку uncompyle6, которая восстанавливает читаемый код (см. следующий вариант).
Ошибка: .pyc файлы привязаны к конкретной версии Python. Попытка загрузить файл из другой версии вызовет ошибку ValueError: bad marshal data. Необходимо использовать ту же версию, в которой файл был создан.
Как декомпилировать .pyc обратно в исходный код Python?
Для восстановления читаемого .py из .pyc применяются декомпиляторы, такие как uncompyle6 (для Python 2.7–3.8) или decompyle3 (для Python 3.9+). Установка:
pip install uncompyle6File manager python (файловый менеджер на python)
Пример использования:
uncompyle6 -o . __pycache__/mymodule.cpython-39.pycMicrosoft vs python (python в visual studio)
В текущую директорию будет записан файл mymodule.py, максимально близкий к оригиналу (без комментариев).
Проблема: декомпиляция не всегда даёт идеальный исходник - теряются комментарии, форматирование, а иногда и части логики в сложных выражениях. Для Python 3.9+ лучшие результаты даёт decompyle3. При несовместимости версий декомпилятор может отказаться работать.
Как полностью отключить создание .pyc файлов?
Чтобы запретить Python записывать .pyc на диск, используется флаг командной строки -B или переменная окружения PYTHONDONTWRITEBYTECODE:
python -B script.py
# или
export PYTHONDONTWRITEBYTECODE=1
python script.py
Microsoft code python (настройка python в visual studio code)
При этом все импорты будут обрабатывать исходный код каждый раз. Полезно для однократных скриптов или при ограничении на запись.
Недостаток: увеличение времени запуска, особенно при большом количестве импортов. Также невозможно будет распространять приложение только в виде .pyc без исходников.
Как изменить директорию, в которую сохраняются .pyc?
Начиная с Python 3.8, переменная окружения PYTHONPYCACHEPREFIX позволяет задать корневую папку для всех .pyc файлов:
export PYTHONPYCACHEPREFIX=/tmp/my_pycache
python script.py
Все .pyc будут создаваться внутри указанной директории, сохраняя оригинальную структуру путей. Это удобно для тестовых сред или контейнеров.
Ограничение: опция доступна только в Python 3.8 и новее. На более старых версиях подобная возможность отсутствует - придётся довольствоваться стандартной папкой __pycache__.
Расширенные примеры работы с .pyc файлами
1. Компиляция с исключением и перезаписью
Параметры -x (исключить по шаблону) и -f (перезаписать) позволяют гибко настроить массовую компиляцию:
python -m compileall -f -x 'test_.*\.py' /home/user/project
Результат: все .py в project компилируются заново, кроме файлов, начинающихся с test_. Вывод на экран:
Compiling /home/user/project/main.py ... Compiling /home/user/project/utils.py ... Skipping /home/user/project/test_main.py (matches pattern)
2. Программная компиляция с обработкой ошибок
Сценарий, который компилирует все файлы в директории, пропуская некорректные:
import compileall, py_compile, sys
def compile_folder(path):
compileall.compile_dir(
path,
force=True,
quiet=1, # подавить сообщения
workers=2, # параллельная компиляция (Python 3.4+)
doraise=False # не прерывать при ошибках
)
compile_folder('/home/user/project')
Результат: все синтаксически верные файлы скомпилированы; ошибки игнорируются, их список можно получить отдельно.
3. Декомпиляция нескольких файлов с помощью скрипта
Скрипт обходит все .pyc в папке и восстанавливает исходники в другую директорию:
import os, subprocess
src_pyc_dir = '__pycache__'
dst_py_dir = 'decompiled'
os.makedirs(dst_py_dir, exist_ok=True)
for file in os.listdir(src_pyc_dir):
if file.endswith('.pyc'):
pyc_path = os.path.join(src_pyc_dir, file)
py_name = file.split('.')[0] + '.py'
subprocess.run(['uncompyle6', '-o', dst_py_dir, pyc_path])
В папке decompiled появятся .py файлы. Для Python 3.9+ замените uncompyle6 на decompyle3.
4. Сравнение производительности: импорт с .pyc и без
Измерение времени первого и повторного импорта:
import timeit
# Первый импорт (создаётся .pyc)
t1 = timeit.timeit('import mymodule', number=1, setup='mymodule = None')
# Повторный импорт (используется .pyc)
t2 = timeit.timeit('import mymodule', number=1, setup='mymodule = None')
print(f'Первый импорт: {t1:.4f} сек')
print(f'Повторный импорт: {t2:.4f} сек')
Типичный результат (на примере проекта с 100 модулями):
Первый импорт: 0.2340 сек Повторный импорт: 0.0121 сек
Разница объясняется тем, что при повторном импорте Python загружает готовый байт-код, минуя синтаксический анализ.
5. Анализ заголовка .pyc файла
Каждый .pyc имеет 16-байтовый заголовок, содержащий магическое число (версия), флаги, timestamp и размер исходного .py. Пример чтения:
import struct, time
with open('__pycache__/demo.cpython-311.pyc', 'rb') as f:
magic = f.read(4)
flags = struct.unpack('
Вывод может быть таким:
Магическое число: 6f0d0d0a Флаги: 0 Время модификации: Mon Mar 14 12:00:00 2024 Размер .py: 342 байт
Магическое число различается между версиями Python; это позволяет интерпретатору отвергать несовместимые .pyc.
6. Компиляция с оптимизацией (-O)
Флаг -O (или -OO) отключает assert'ы и docstrings, уменьшая размер .pyc и ускоряя выполнение:
python -O -m py_compile script.py
Файл .pyc в __pycache__ будет иметь имя script.cpython-311.opt-1.pyc. Сравнение размера:
Без -O: 1024 байт С -O : 870 байт С -OO: 800 байт (ещё удалены docstrings)
Использование оптимизированных .pyc экономит память и время загрузки, но делает код менее отлаживаемым (assert'ы игнорируются).
7. Создание .pyc в оперативной памяти (без записи на диск)
Модуль py_compile позволяет получить байт-код в виде строки, не сохраняя файл:
import py_compile, io
# читаем исходный код
with open('script.py', 'r') as f:
source = f.read()
# компилируем в байт-код
code = compile(source, 'script.py', 'exec')
# имитация .pyc через marshal
import marshal
with open('script_bytecode.pyc', 'wb') as f:
f.write(marshal.dumps(code))
Такой подход используется для динамической загрузки кода, например, в плагинах или серверных приложениях, где не нужно создавать реальные файлы.