Самое, наверное, заметное отличие компилируемых языков от… — Остальные 90% — TG.ME

Самое, наверное, заметное отличие компилируемых языков от интерпретируемых, заключается в возможности прочитать код программы. Чтобы понять по исполняемому файлу, что он делает, нужно применять специальные инструменты - отладчики, шестнадцатеричные редакторы, дизассемблеры - и долго и упорно изучать вывод этих команд. В то же время скрипты и библиотеки Python можно спокойно прочитать и даже поправить без вреда для программы. Это очень удобно для отладки, однако иногда всё-таки хочется сохранить код в секрете от пользователя.

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

И всё-таки в случае с Python можно обойтись без таких ухищрений, тем более, что восстановить исходные коды после подобных преобразований не столь сложно. При ближайшем рассмотрении Python, как и многие другие интерпретируемые языки, перед исполнением скрипта компилирует его в байт-код, который уже исполняется виртуальной машиной интерпретатора. После запуска любого проекта, написанного на Python, в рабочей директории создаётся директория __pycache__/, содержащая .pyc -файлы - скомпилированные представления каждого из модулей:
$ ls -1
main.py
utils.py

$ cat *
# main.py
import utils

print(utils.hi)

# utils.py
hi = 'hi'

$ python3 main.py
hi

$ ls -R1
.:
__pycache__
main.py
utils.py

./__pycache__:
utils.cpython-312.pyc


За компиляцию отвечают встроенные модули Python py_compile и compileall, которые можно вызвать явно, что мы и сделаем, чтобы скомпилировать файл main.py:
$ python3 -m py_compile main.py
$ ls -1 __pycache__
main.cpython-312.pyc
utils.cpython-312.pyc


Это представление уже достаточно надёжно "прячет" код от пользователя, а главное, его можно использовать и в отсутствие файлов с исходным кодом, что в значительной мере решает поставленную задачу:
# Remove source code
$ rm *.py

# Place .pyc-files in place of .py
$ mv __pycache__/* .
$ rm -rf __pycache__; ls -1
main.cpython-312.pyc
utils.cpython-312.pyc

# Run!
$ python3 main.cpython-312.pyc
hi


Решение, прибегающее к "sourceless Python", всегда будет требовать той же версии Python, для которой был сгенерирован байт-код: промежуточное представление кода часто меняется, и Python3.12 почти наверное не сможет прочитать .pyc -файл версии 3.11. В прочем, если код поставляется в виде docker-образа, то согласовать версию интерпретатора с байт-кодом будет не так сложно.

Пользователь также может попытаться восстановить исходный код с помощью декомпилятора, например, decompyle, но угнаться за стандартом не так-то просто - байт-код для версий свежее, чем 3.8, восстановить на момент написания заметки скорее всего pycache
$ decompyle3 __pycache__/utils.cpython-312.pyc 
...
Unsupported Python version, 3.12.0, for decompilation
...
❤6👍5🔥4
March 3, 2025 714 4 7