- Объяснять понятия модуля, пакета,
sys.pathиsys.modules - Использовать
if __name__ == '__main__', чтобы файл работал и как модуль, и как скрипт - Создавать виртуальное окружение через
venv, устанавливать пакеты черезpipи фиксировать зависимости - Читать
requirements.txtиpyproject.tomlи понимать ограничения версий
Одному проекту нужен Django 4, другому — Django 5: установишь пакет на весь компьютер, и один из проектов обязательно сломается. Коллега запускает твой код и получает ModuleNotFoundError, потому что установленных тобой библиотек у него нет. Настоящие проекты следуют трём правилам: код делится на модули и пакеты, у каждого проекта своё виртуальное окружение, а зависимости записаны в файле вместе с версиями.
Модули и пакеты
Модуль — любой файл .py. Когда ты пишешь import pricing, Python ищет файл в папках из списка sys.path, один раз выполняет его код верхнего уровня и сохраняет объект модуля в sys.modules — последующие импорты берут готовый объект. Пакет — папка с модулями; в обычном пакете есть файл __init__.py, который выполняется при первом импорте пакета. Имена записываются через точку: shop.pricing — модуль pricing в пакете shop. Типичный проект выглядит так:
shop/
├── pyproject.toml
├── requirements.txt
├── .venv/ (not in git)
├── src/
│ └── shop/
│ ├── __init__.py
│ ├── pricing.py
│ └── cli.py
└── tests/
└── test_pricing.pysrc/, а тесты — отдельно в tests/.Пример ниже создаёт маленький пакет во временной папке и импортирует его тремя разными способами (строки sys.modules.pop нужны только для того, чтобы пример можно было запускать повторно):
import importlib
import sys
import tempfile
from pathlib import Path
root = Path(tempfile.mkdtemp())
(root / 'shop').mkdir()
(root / 'shop' / '__init__.py').write_text('from .pricing import final_price\n')
(root / 'shop' / 'pricing.py').write_text(
'VAT = 0.18\n'
'print("running pricing.py as", __name__)\n'
'def final_price(net):\n'
' return round(net * (1 + VAT), 2)\n'
)
sys.path.insert(0, str(root))
sys.modules.pop('shop', None)
sys.modules.pop('shop.pricing', None)
importlib.invalidate_caches()
import shop
import shop.pricing
from shop import final_price
print(final_price(100), shop.pricing.VAT)
print(__name__)▸ Ожидаемый результат
running pricing.py as shop.pricing 118.0 0.18 __main__
pricing.py выполнился только один раз, несмотря на три импорта. Внутри модуля __name__ — его полное имя (shop.pricing), а в файле, запущенном напрямую, — '__main__'. Строка from .pricing import final_price в __init__.py — относительный импорт: точка означает «этот пакет». Благодаря ей пользователи могут писать from shop import final_price, не зная внутреннего устройства пакета.
if __name__ == '__main__': скрипт или модуль
Файл может быть и импортируемым модулем, и запускаемым скриптом. Условие if __name__ == '__main__': выполняет код скрипта, только когда файл запущен напрямую, а не при импорте. Команда python -m shop.cli находит модуль как часть пакета и запускает его как скрипт, поэтому импорты внутри пакета продолжают работать:
import sys
from shop.pricing import final_price
def main() -> int:
for arg in sys.argv[1:]:
print(f'{arg} -> {final_price(float(arg))}')
return 0
if __name__ == '__main__':
sys.exit(main())src/shop/cli.py$ python -m shop.cli 10 25.5
10 -> 11.8
25.5 -> 30.09Виртуальные окружения: venv и pip
Без виртуального окружения все проекты делят одну папку site-packages, и версии пакетов конфликтуют. Виртуальное окружение — папка внутри проекта (обычно .venv) со своим интерпретатором Python и своими пакетами. Её создаёт стандартный модуль venv, а пакеты в неё устанавливает pip:
- 1Создай
В папке проекта:
python -m venv .venv. - 2Активируй
Windows:
.venv\Scripts\activate; macOS и Linux:source .venv/bin/activate. В начале командной строки появится(.venv). - 3Установи
python -m pip install requests— пакет попадает только в это окружение. - 4Зафиксируй
python -m pip freeze > requirements.txtзаписывает в файл точные версии установленных пакетов. - 5Воспроизведи
Коллега создаёт своё окружение и выполняет
python -m pip install -r requirements.txt. - 6Выйди
Команда
deactivateвозвращает обычную командную строку.
$ python -m venv .venv
$ source .venv/bin/activate
(.venv) $ python -m pip list
Package Version
------- -------
pip 25.2
(.venv) $ python -m pip install requests
Collecting requests
...
Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests
Successfully installed certifi-2024.8.30 charset-normalizer-3.4.0 idna-3.10 requests-2.32.3 urllib3-2.2.3
(.venv) $ python -m pip freeze
certifi==2024.8.30
charset-normalizer==3.4.0
idna==3.10
requests==2.32.3
urllib3==2.2.3requests приносит с собой свои зависимости. Номера версий будут зависеть от дня установки.requirements.txt и pyproject.toml
requirements.txt — простой список: по одному пакету в строке, при желании с ограничением версии. Для приложений принято закреплять точные версии (==), чтобы программа на любом компьютере работала с одними и теми же библиотеками:
# requirements.txt
requests==2.32.3
fastapi>=0.110,<1.0
python-dotenv~=1.0| Запись | Значение |
|---|---|
==2.32.3 | ровно эта версия |
>=0.110,<1.0 | диапазон: 0.110 или новее, но ниже 1.0 |
~=1.4 | совместимый выпуск: >=1.4 и <2.0 |
| (нет) | любая версия — рискованно |
Главный файл современного проекта — **pyproject.toml**: в нём записаны имя, версия, поддерживаемые версии Python, зависимости, инструменты только для разработки и скрипты командной строки. Команда python -m pip install -e . в папке проекта устанавливает пакет в «редактируемом» режиме: после изменений кода переустанавливать ничего не нужно, а команда shop из [project.scripts] сразу работает.
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "shop"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = ["requests>=2.31"]
[project.optional-dependencies]
dev = ["pytest>=8"]
[project.scripts]
shop = "shop.cli:main"pyproject.toml (формат TOML). Инструменты разработки устанавливаются командой python -m pip install -e ".[dev]".Напиши parse_requirements(text): она возвращает словарь {пакет: ограничение} по тексту requirements.txt. Пропускай пустые строки и комментарии, начинающиеся с #; если ограничения нет, значение — 'any version'.
import re
requirements = '''
# web
requests==2.32.3
fastapi>=0.110,<1.0
python-dotenv~=1.0
numpy
'''
def parse_requirements(text):
result = {}
# go through the lines, skip blanks and comments,
# split each line into the package name and the specifier
return result
for name, spec in parse_requirements(requirements).items():
print(f'{name}: {spec}')▸ Ожидаемый результат
requests: ==2.32.3 fastapi: >=0.110,<1.0 python-dotenv: ~=1.0 numpy: any version
Сортировать версии как строки неправильно: '0.10.0' оказывается раньше '0.9.3'. Напиши функцию version_key, чтобы версии сортировались правильно и печаталась самая новая.
versions = ['0.10.0', '0.9.3', '1.0.0', '0.1.0', '0.9.12']
def version_key(v):
# turn '0.10.0' into something that compares correctly
return v
print(sorted(versions))
print(sorted(versions, key=version_key))
print('latest:', max(versions, key=version_key))▸ Ожидаемый результат
['0.1.0', '0.10.0', '0.9.12', '0.9.3', '1.0.0'] ['0.1.0', '0.9.3', '0.9.12', '0.10.0', '1.0.0'] latest: 1.0.0
Главное
- Модуль — файл
.py, пакет — папка с модулями; код модуля выполняется один раз, при первом импорте, и кэшируется вsys.modules. __name__равно'__main__'в файле, запущенном напрямую;if __name__ == '__main__':не даёт коду скрипта выполняться при импорте.- У каждого проекта своё виртуальное окружение:
python -m venv .venv, активация,python -m pip install .... - Зависимости записываются в
requirements.txtилиpyproject.toml;==,>=,<и~=ограничивают версии. .venvне попадает в git: окружение всегда пересоздаётся по файлу зависимостей.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
python -m venv .venv?