Практическое руководство: создаем пакет в Python
Создание пакета в Python
Пакет в Python позволяет организовать связанные модули в единую структуру, упрощая импорт и распространение кода. Ниже рассмотрены основные способы создания пакетов, их преимущества и типичные затруднения.
Как создать минимальный пакет с возможностью установки через pip?
Наиболее распространенным способом является создание структуры каталогов с файлом __init__.py и конфигурационным файлом setup.py. Этот подход поддерживается большинством инструментов и позволяет устанавливать пакет в систему или виртуальное окружение.
Пошаговая инструкция:
- Создайте папку проекта, например
my_package/Python package init (инициализация пакета python (__init__.py))
- Внутри папки создайте подкаталог с именем пакета
mypkg/Python create package (создание пакета в python)
и поместите в него файл
(может быть пустым).__init__.py - Добавьте в
модули, напримерmypkg/
.module1.py - В корне проекта создайте файл
с минимальной информацией.setup.py
Пример содержимого
setup.py:from setuptools import setup, find_packages
setup(
name='mypkg',
version='0.1.0',
packages=find_packages(),
)Теперь пакет можно установить в режиме редактирования:
pip install -e .После установки импорт работает:
from mypkg import module1Типичная ошибка: ImportError: No module named 'mypkg' возникает, если файл __init__.py отсутствует в каталоге пакета или если find_packages() не находит подпакеты. Решение: убедиться, что все папки содержат __init__.py (даже пустой).
Еще одна проблема: конфликт имен пакета с уже установленным пакетом. Перед публикацией рекомендуется проверить уникальность имени на PyPI.
Как создать пакет без файла setup.py (только для локального использования)?
Если нет необходимости устанавливать пакет в окружение, достаточно просто создать папку с __init__.py и модулями. Тогда пакет можно импортировать, если интерпретатор запускается из родительского каталога. Этот подход удобен для небольших проектов без внешних зависимостей.
Пример структуры:
project/
├── mypkg/
│ ├── __init__.py
│ └── utils.py
└── main.pyВ
main.py импорт выполняется как from mypkg import utils. Однако такой пакет не будет доступен из произвольного места системы, и его невозможно легко передать другим разработчикам.Проблема: импорт может не сработать, если текущая рабочая директория не является родительской для пакета. Решение: использовать относительные пути или временно добавлять путь в sys.path.
Как использовать pyproject.toml вместо setup.py?
Современные проекты переходят на стандарт PEP 621, где метаданные указываются в файле pyproject.toml. Для этого потребуется бэкенд, например, setuptools или flit.
Пример pyproject.toml для setuptools:
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "mypkg"
version = "0.1.0"Установка выполняется аналогично:
pip install -e .Этот подход считается более чистым, так как отделяет конфигурацию сборки от Python кода.
Ошибка: при использовании pyproject.toml без setup.py может возникнуть предупреждение о том, что файл не найден, если бэкенд не указан. Решение: всегда указывать [build-system].
Как создать пакет с подпакетами (вложенная структура)?
Для организации иерархии модулей внутри пакета достаточно создать вложенные каталоги с собственными файлами __init__.py. Например:
mypkg/
├── __init__.py
├── core/
│ ├── __init__.py
│ └── algorithms.py
└── io/
├── __init__.py
└── readers.pyВ setup.py для поиска всех подпакетов используется find_packages(where='.') или find_packages() без аргументов.
Импортировать вложенные модули можно так:
from mypkg.core import algorithmsРаспространенная проблема: подпакеты не импортируются, если в setup.py не указан параметр packages явно. Всегда используйте find_packages().
Расширенные примеры создания пакетов
Ниже представлены более сложные сценарии: пакет с конфигурационными данными, тестами и точкой входа.
Пакет с вложенными модулями и данными
Создадим пакет datapack, содержащий текстовые данные и утилиты для их загрузки.
Структура:
datapack/
├── pyproject.toml
├── src/
│ └── datapack/
│ ├── __init__.py
│ ├── loader.py
│ └── data/
│ └── names.txt
└── tests/
└── test_loader.pyФайл pyproject.toml:
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "datapack"
version = "0.1.0"
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
"datapack" = ["data/*.txt"]Файл loader.py:
import os
def load_names():
path = os.path.join(os.path.dirname(__file__), 'data', 'names.txt')
with open(path, 'r') as f:
return [line.strip() for line in f if line.strip()]Установка и проверка:
pip install -e .
python -c "from datapack.loader import load_names; print(load_names())"['Alice', 'Bob', 'Charlie']
Тест tests/test_loader.py:
from datapack.loader import load_names
def test_load_names():
names = load_names()
assert len(names) > 0
assert 'Alice' in namesПакет с точкой входа (консольным скриптом)
Добавить исполняемый скрипт можно через параметр entry_points в setup.py или [project.scripts] в pyproject.toml.
Пример pyproject.toml:
[project]
name = "greeter"
version = "0.1.0"
[project.scripts]
greet = "greeter.cli:main"Модуль greeter/cli.py:
def main():
print("Hello from greeter!")После установки команда
greet выводит:Hello from greeter!
Пакет с использованием namespace (устаревший подход)
Namespace пакеты позволяют распределять код по нескольким независимым дистрибутивам. Для этого в __init__.py не должно быть кода, а в setup.py указывается namespace_packages.
Пример: два пакета com.example.foo и com.example.bar с общим пространством com.example.
Структура для com.example.foo:
com/
└── example/
└── foo/
└── __init__.pyФайл setup.py для foo:
from setuptools import setup
setup(
name='com.example.foo',
version='1.0',
packages=['com.example.foo'],
namespace_packages=['com.example']
)Важно: этот способ считается устаревшим. В современных проектах рекомендуется использовать implicit namespace packages (просто папки без __init__.py) и PEP 420.
Установка обоих пакетов позволяет выполнить: from com.example.foo import something from com.example.bar import something_else