MEASUREMENT
bench/venv/practice.py
The script that produced the numbers in the article, and the record of the run. The file is read from the repository at build time — this is the code that was run, not a copy of it.
- Cited in
- /en/interview/python/venv-dependencies
- How to run it
python3.11 bench/venv/basics.py python3.12 bench/venv/basics.py python3.13 bench/venv/basics.py python3.14 bench/venv/basics.py python3.11 bench/venv/practice.py python3.12 bench/venv/practice.py python3.13 bench/venv/practice.py python3.14 bench/venv/practice.py
The run below is recorded in Russian. It is a lab record, kept in the language it was written in; the numbers, the tables and the code read the same either way.
Record of the run
Замеры для урока «Окружения и зависимости»
| Скрипт | Что делает |
|---|---|
basics.py |
создаёт окружение во временной папке и смотрит на него: что в папке и в pyvenv.cfg, sys.prefix без активации, пакеты в свежем окружении, изоляция поставленного пакета и pip freeze, что меняет activate, что ломается в копии окружения, что пишет pip freeze для пакетов из PyPI, из локальной папки и editable, и что видит env/bin/python -S |
practice.py |
источник ответов двух практических задач урока |
python3.11 bench/venv/basics.py
python3.12 bench/venv/basics.py
python3.13 bench/venv/basics.py
python3.14 bench/venv/basics.py
python3.11 bench/venv/practice.py
python3.12 bench/venv/practice.py
python3.13 bench/venv/practice.py
python3.14 bench/venv/practice.py
Запускать самим интерпретатором, а не символической ссылкой на него: venv
определяет базовую установку по пути интерпретатора. Раздел 4 basics.py
ставит tomli-w==1.0.0 из PyPI, раздел 7 скачивает setuptools для сборки
двух локальных пакетов — нужна сеть.
Записи в runs/: без суффикса — CPython 3.13.7, остальные — с суффиксом
версии (3.11.15, 3.12.3, 3.14.7). Путь временной папки в выводе заменён на
<папка>, путь базовой установки — на <python>.
Что получилось
Папка окружения: bin/, include/, lib/, lib64 (символическая
ссылка на lib), pyvenv.cfg; с 3.13 — ещё .gitignore (его создаёт
командная строка python -m venv). bin/python — символическая ссылка на
базовый интерпретатор; абсолютный путь окружения записан в bin/activate и в
первой строке bin/pip (#!<папка>/env/bin/python3.13). На 3.14 в bin/
есть ещё имя 𝜋thon.
Без активации: sys.prefix — папка окружения, sys.base_prefix —
базовая установка, пакеты ставятся в site-packages окружения.
Свежее окружение: 3.11 — pip==24.0 и setuptools==79.0.1; 3.12 —
pip==24.0; 3.13 — pip==25.2; 3.14 — pip==26.2.1. Наполняет его
ensurepip; набор пакетов задан в Lib/ensurepip/__init__.py каждой версии.
Изоляция: tomli_w импортируется интерпретатором окружения и не
импортируется базовым (ModuleNotFoundError). У базового sys.prefix —
<python>, пакеты — в <python>/lib/python3.13/site-packages (у сборок 3.11
и 3.12 из Ubuntu — <python>/local/lib/python3.1X/dist-packages). В
метаданных пакета Name: tomli_w; pip freeze — tomli_w==1.0.0, без
самого pip (на 3.11 — и без setuptools).
activate: ставит env/bin первым в PATH, задаёт VIRTUAL_ENV и
VIRTUAL_ENV_PROMPT (env на 3.13 и 3.14, (env) на 3.11 и 3.12);
deactivate возвращает PATH прежним. Что ещё делает шаблон activate —
приглашение PS1, снятие PYTHONHOME — прогон не печатает.
Копия в другую папку: moved/bin/python ставит sys.prefix на
<папка>/moved, а moved/bin/pip по шебангу работает со старым окружением
(<папка>/env/lib/…/pip); moved/bin/python -m pip — с копией. Когда старой
папки нет, moved/bin/pip не запускается: FileNotFoundError. Одинаково на
3.11–3.14.
pip freeze (раздел 7): снимок в формате requirements — tomli_w==1.0.0
для пакета из PyPI, localpkg @ file://<папка>/localpkg для пакета из
локальной папки, # Editable install with no version control (editpkg==0.1)
и -e <папка>/editpkg для editable. --all добавляет то, что freeze
пропускает: на 3.11 — pip==24.0 и setuptools==79.0.1, на 3.12–3.14 —
только pip. Правило пропуска — pip/_internal/commands/freeze.py (pip 24.0:
строки 12–20, по sys.version_info < (3, 12) к pip добавляются
setuptools, distribute, wheel).
env/bin/python -S (раздел 8): на 3.11–3.13 sys.prefix — <python>,
prefix != base_prefix — False; на 3.14 — <папка>/env и True. С 3.14
префиксы окружения выставляет инициализация путей, а не site (документация
site 3.14, «Changed in version 3.14»). site-packages окружения в
sys.path под -S нет ни на одной версии.
Script
43 lines"""Практика урока «Окружения и зависимости»: источник ответов двух задач.
Ответ задачи не сочиняется редакцией — он берётся из записи прогона этого
скрипта (`runs/practice.txt`), а сборка сверяет одно с другим
(`scripts/validate-practice.mjs`).
ЧТО ПРОВЕРЯЮТ ЗАДАЧИ. Первая — что интерпретатор окружения знает, что он в
окружении, и без активации: код задачи выполняется через env/bin/python,
activate не вызывается. Вторая — сколько пакетов лежит в свежем окружении.
Запускать: python3.11 / 3.12 / 3.13 / 3.14 (самим интерпретатором, не
символической ссылкой на него).
"""
import os
import subprocess
import sys
import tempfile
print("PY", sys.version.split()[0])
print()
tmp = tempfile.mkdtemp()
env_dir = os.path.join(tmp, "env")
subprocess.run([sys.executable, "-m", "venv", env_dir], check=True)
env_py = os.path.join(env_dir, "bin", "python")
TASK = """
import sys
print(sys.prefix == sys.base_prefix)
print(sys.prefix.endswith("env"))
"""
print("вывод задачи:")
print(subprocess.run([env_py, "-c", TASK], capture_output=True, text=True, check=True).stdout.rstrip())
print()
print("оценка:")
listed = subprocess.run(
[env_py, "-m", "pip", "list", "--format=freeze", "--disable-pip-version-check"],
capture_output=True, text=True, check=True,
).stdout.split()
print("пакетов в свежем окружении:", len(listed))