Практическое руководство: создаем пакет в Python

Раздел: Разработка на Python -> Создание пакетов

Создание пакета в Python

Пакет в Python позволяет организовать связанные модули в единую структуру, упрощая импорт и распространение кода. Ниже рассмотрены основные способы создания пакетов, их преимущества и типичные затруднения.

Как создать минимальный пакет с возможностью установки через pip?

Наиболее распространенным способом является создание структуры каталогов с файлом __init__.py и конфигурационным файлом setup.py. Этот подход поддерживается большинством инструментов и позволяет устанавливать пакет в систему или виртуальное окружение.

Пошаговая инструкция:

  1. Создайте папку проекта, например
    my_package/

    Python package init (инициализация пакета python (__init__.py))

  2. Внутри папки создайте подкаталог с именем пакета
    mypkg/

    Python create package (создание пакета в python)

    и поместите в него файл
    __init__.py
    (может быть пустым).
  3. Добавьте в
    mypkg/
    модули, например
    module1.py
    .
  4. В корне проекта создайте файл
    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

Создание пакета в Python - comments

En
Python create package (python)