Файлы .pyc в Python: компиляция, декомпиляция и управление байт-кодом

Раздел: Python -> IDE и редакторы

Основные способы работы с .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 mymodule

Compiled 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 uncompyle6

File manager python (файловый менеджер на python)

Пример использования:

uncompyle6 -o . __pycache__/mymodule.cpython-39.pyc

Microsoft 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__.

- компилятор python с библиотеками (компиляция python с библиотеками (pyinstaller, cx_freeze))
- редактор python (редактор для python)
- Python py exe (создание exe-файла из python скрипта)

Расширенные примеры работы с .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))

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

Скомпилированные файлы Python (.pyc) - comments

En
Compiled python file (python)