embeddable Python: портативный интерпретатор рядом с программой
Интерпретатор в одной папке с программой: что внутри embeddable-пакета python.org, как читается ._pth-конфигурация и почему site-packages выключен по умолчанию.
Редакция Питоники
Архив .pyz из прошлого урока упаковал код, но требовал установленного Python. Следующий вопрос логичен: а если у пользователя Python нет? Ставить ему целый Python ради скрипта — тяжело и рискованно: установка может затронуть другие проекты. Легковесное решение предлагает сам python.org — embeddable-пакет: zip-архив, в котором интерпретатор и стандартная библиотека. Распаковали его в папку рядом с программой — и программа работает без установки. Разберём, что внутри и почему это работает.
Что внутри embeddable-пакета
На странице загрузок python.org пакет называется Windows embeddable package. Это не установщик, а просто zip: внутри — исполняемые файлы интерпретатора, ядро и стандартная библиотека, причём библиотека лежит одним zip-архивом:
python-3.13.11-embed-amd64/
|-- python.exe <- интерпретатор
|-- pythonw.exe <- без консоли (для GUI)
|-- python313.dll <- ядро CPython
|-- python313.zip <- стандартная библиотека архивом
|-- python313._pth <- конфигурация путей
|-- libcrypto-3-x64.dll <- SSL: нужен pip и https
|-- libssl-3-x64.dll
`-- sqlite3.dll
Два наблюдения. Первое: имена файлов содержат версию — python313.dll и python313.zip у 3.13, у других версий номер другой. Второе: стандартная библиотека лежит архивом, и читает её знакомый по прошлому уроку механизм zipimport — только теперь это библиотека самого Python.
python313._pth — карта путей
Ключ к поведению embeddable — маленький файл python313._pth. Он заменяет стандартный поиск путей: каждая строка — путь относительно папки интерпретатора, строки с решёткой — комментарии, а особая строка import site управляет загрузкой site-packages. Разберём заводской файл построчно — так, как его читает Python при старте:
config = [
'python313.zip',
'.',
'# import site',
]
paths = []
site_enabled = False
for line in config:
line = line.strip()
if not line or line.startswith('#'):
continue
if line == 'import site':
site_enabled = True
continue
paths.append(line)
print('Пути поиска модулей:', paths)
print('site импортируется:', site_enabled)
Пути поиска модулей: ['python313.zip', '.'] site импортируется: False
Получилось два пути: архив стандартной библиотеки и точка — сама папка интерпретатора. А site выключен: строка import site в заводском файле закомментирована. Это осознанная изоляция — embeddable по умолчанию не видит ни системные пакеты, ни другие установки Python. То, что для обычного Python было бы багом, здесь — фича: портативная программа не зависит от того, что стоит на машине.
def resolve(config):
paths = []
site_enabled = False
for line in config:
line = line.strip()
if not line or line.startswith('#'):
continue
if line == 'import site':
site_enabled = True
continue
paths.append(line)
return paths, site_enabled
factory = ['python313.zip', '.', '# import site']
enabled = ['python313.zip', '.', 'site-packages', 'import site']
print('Заводская настройка:', resolve(factory))
print('После включения site:', resolve(enabled))
Заводская настройка: (['python313.zip', '.'], False) После включения site: (['python313.zip', '.', 'site-packages'], True)
Раскомментировали import site — включили загрузку site-packages, а строка site-packages добавила папку сторонних пакетов в поиск. Именно так в embeddable подключают зависимости: своими руками, строками в ._pth.
import sys, zipfile, os, importlib
with zipfile.ZipFile('stdlib.zip', 'w') as z:
z.writestr('runtime_greet.py', "def hello():\n return 'Модуль найден в архиве'\n")
try:
importlib.import_module('runtime_greet')
print('Без архива в пути: модуль найден')
except ModuleNotFoundError:
print('Без архива в пути: модуль не найден')
sys.path.insert(0, 'stdlib.zip')
greet = importlib.import_module('runtime_greet')
print('Через архив в пути:', greet.hello())
sys.path.remove('stdlib.zip')
os.remove('stdlib.zip')
Без архива в пути: модуль не найден Через архив в пути: Модуль найден в архиве
Это и есть механика python313.zip: пока архив не в пути поиска, модуля не существует; добавили архив — модуль импортировался. Интерпретатор embeddable делает эту запись за вас при каждом старте, руководствуясь ._pth.
pip в embeddable: подключаем руками
Пакетного менеджера в embeddable нет: pip в комплект не входит, и ensurepip туда тоже не включён. Классический рецепт из документации сообщества:
# 1) в python313._pth раскомментировать строку import site
# 2) скачать get-pip.py и поставить pip:
python get-pip.py
# 3) ставить пакеты как обычно:
python -m pip install requests
После этого pip ставит пакеты в site-packages рядом с интерпретатором — ту самую папку, которую мы включали строкой в ._pth. Ставить пакеты нужно именно этим интерпретатором: python.exe из папки runtime, а не системный.
Запуск программы портативным интерпретатором
Программа в этой схеме остаётся обычными .py-файлами в своей папке, а интерпретатор живёт рядом, в папке runtime. Запускают не двойным кликом по python.exe, а лаунчером — маленьким bat-файлом рядом с папками:
@echo off
start "" "%~dp0runtime\python.exe" "%~dp0app\main.py" %*
Соберём такую структуру и убедимся, что точка входа запускается. В песочнице вместо runtime/python.exe работу сделает runpy, но структура — именно та, что окажется на машине пользователя:
import os, runpy, shutil
os.makedirs('mytool/app', exist_ok=True)
os.makedirs('mytool/runtime', exist_ok=True)
with open('mytool/app/main.py', 'w', encoding='utf-8') as f:
f.write("print('Инвентарь: 3 позиции')\n")
launcher = os.path.join('runtime', 'python.exe') + ' ' + os.path.join('app', 'main.py')
with open(os.path.join('mytool', 'run.bat'), 'w', encoding='utf-8') as f:
f.write('@echo off\n' + launcher + '\n')
listed = []
for root, dirs, files in os.walk('mytool'):
for name in files:
listed.append(root.replace(os.sep, '/') + '/' + name)
print('\n'.join(sorted(listed)))
runpy.run_path(os.path.join('mytool', 'app', 'main.py'), run_name='__main__')
shutil.rmtree('mytool')
mytool/app/main.py mytool/run.bat Инвентарь: 3 позиции
Почему это работает: префикс
Обычный Python при старте ищет свои файлы по путям, записанным при установке. embeddable устроен иначе: он считает домом папку, где лежит python.exe, — она называется префиксом. Рядом с префиксом — ._pth, рядом — архив стандартной библиотеки:
import os
def detect_prefix(executable):
return os.path.dirname(executable)
exe = 'D:/apps/mytool/runtime/python.exe'
prefix = detect_prefix(exe)
print('Интерпретатор:', exe)
print('Префикс рантайма:', prefix)
print('Конфигурация рядом:', prefix + '/python313._pth')
Интерпретатор: D:/apps/mytool/runtime/python.exe Префикс рантайма: D:/apps/mytool/runtime Конфигурация рядом: D:/apps/mytool/runtime/python313._pth
embeddable считает домом папку, где лежит python.exe: пути относительные, реестр не используется, и всю папку можно переносить на флешке. Перенесли — интерпретатор заново нашёл свои файлы относительно нового места, и программа продолжает работать.
Сравнение с exe-сборкой
| Критерий | embeddable рядом | exe-сборка (PyInstaller) |
|---|---|---|
| Что распространяете | Папка: интерпретатор плюс ваши .py | Один exe или папка dist |
| Правки после выпуска | Отредактировать .py — и готово | Пересборка |
| Обновление Python | Заменить папку runtime | Пересобрать новой версией |
| Исходники видны | Да: программа открыта | Нет: внутри байткод |
| Установка пользователю | Не нужна | Не нужна |
Границы: embeddable-пакет существует только для Windows. На Linux и macOS ту же роль играют виртуальные окружения рядом с программой или сторонние портативные сборки. И разрядность обязана совпадать: 64-битный пакет работает на 64-битной системе.
Получается лестница: zipapp упаковал код, но требовал установленного Python; embeddable добавляет интерпретатор в комплект; следующий урок про PyInstaller — про самый популярный способ склеить код и интерпретатор в один exe.
Сначала предскажи ответ в голове — это главный навык программиста.
config = [
'python313.zip',
'# это комментарий',
'.',
'# import site',
]
paths = []
site_enabled = False
for line in config:
line = line.strip()
if not line or line.startswith('#'):
continue
if line == 'import site':
site_enabled = True
continue
paths.append(line)
print('Пути:', paths)
print('site:', site_enabled)
import sys, zipfile, os, importlib
with zipfile.ZipFile('lib.zip', 'w') as z:
z.writestr('marker9.py', "FLAG = 'внутри архива'\n")
sys.path.insert(0, 'lib.zip')
m = importlib.import_module('marker9')
print(m.FLAG)
sys.path.remove('lib.zip')
os.remove('lib.zip')
import os
def detect_prefix(executable):
return os.path.dirname(executable)
print(detect_prefix('C:/portable/runtime/python.exe'))
1. Что такое embeddable-пакет Python?
2. Что делает файл python313._pth в embeddable-пакете?
3. Пакет поставили через pip, а программа пишет, что модуль не найден. Первая версия?
4. Где embeddable-интерпретатор берёт стандартную библиотеку?
5. Почему портативную папку можно переносить на другой диск?
6. Чем портативная схема лучше exe-сборки для сопровождения?
7. На каких системах работает официальный embeddable-пакет?
Собери каркас портативного приложения: папка myapp с конфигурацией python._pth (строки python313.zip и .) и точкой входа app/main.py. Прочитай ._pth как интерпретатор, напечатай пути и запусти main.py. Ожидаемый вывод: пути и строка о запуске.
Что такое embeddable Python простыми словами?
Это zip с python.org, в котором лежит готовый интерпретатор: python.exe, ядро и стандартная библиотека архивом. Распаковываете папку рядом со своей программой — и она запускается на машине без установленного Python.
Урок 8: zipapp — скрипт-архив .pyzЧем embeddable отличается от обычной установки Python?
Три отличия: нет установки и реестра — папка переносится как есть; поиск путей ведёт ._pth-файл, а не системная конфигурация; site-packages выключены и pip не входит в комплект — всё подключается руками.
Как добавить pip в embeddable Python?
Раскомментировать строку import site в ._pth, скачать get-pip.py и запустить его этим интерпретатором: python get-pip.py. После этого python -m pip install работает, а пакеты попадают в site-packages рядом с runtime.
Подходит ли embeddable для macOS и Linux?
Нет: официальный embeddable-пакет выпускается только для Windows. На других системах ту же задачу решают виртуальным окружением рядом с программой или сторонними портативными сборками интерпретатора.
Урок 16: реализации PythonПонравился урок? Сошлитесь на него
«embeddable считает домом папку, где лежит python.exe: пути относительные, реестр не используется, и всю папку можно переносить на флешке.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Компиляция · Урок 8
zipapp: скрипт-архив .pyz — программа в одном файле
Один файл вместо папки: собираем запускаемый zip-архив с точкой входа __main__.py, дописываем shebang и разбираем, когда .pyz достаточно без exe.
Компиляция · Урок 10
PyInstaller: сборка exe из Python-скрипта
От скрипта к exe: PyInstaller анализирует импорты, кладёт интерпретатор, байткод и данные в один файл. Собираем бандл по шагам и проверяем его на чистой машине.
Компиляция · Урок 16
Реализации Python: CPython и альтернативные среды
Python — это язык, а CPython — его главная реализация. Разбираем, кто ещё исполняет Python-код, как узнать свою среду через sys.implementation и что гарантировано любому коду на любой реализации.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
FastAPI · Урок 2
Первое приложение на FastAPI: маршруты, uvicorn и Swagger
Устанавливаем FastAPI, пишем первый эндпоинт, запускаем сервер uvicorn и открываем Swagger — документацию, которая пишет себя сама.
fastapi первое приложение
FastAPI · Урок 5
CRUD на FastAPI: POST, PUT, PATCH и DELETE методы
Строим полный CRUD на FastAPI: POST со статусом 201, PUT с защитой от 404, PATCH и DELETE — и собираем мини-сервис задач целиком.
crud приложение python
Компиляция · Урок 11
PyInstaller onefile против onedir: что выбрать
Один exe или exe с папкой _internal: как onefile и onedir влияют на старт, обновление, антивирусы и поиск файлов данных — с механикой бутстрапа на живом коде.
pyinstaller данные рядом с exe