Как написать и опубликовать свою библиотеку Python

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

Основные этапы создания библиотеки Python

Наиболее эффективный и современный подход к созданию библиотеки Python заключается в использовании pyproject.toml совместно с инструментом build и менеджером setuptools. Этот способ рекомендован официальной документацией Python и поддерживает гибкое управление метаданными, зависимостями и версиями.

Как организовать структуру проекта для будущей библиотеки?

Стандартная структура выглядит так:


my_library/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│   └── my_library/
│       ├── __init__.py
│       ├── module1.py
│       └── module2.py
└── tests/
    ├── __init__.py
    └── test_module1.py
  

создание библиотеки python (создание библиотеки python)

Папка src облегчает тестирование и избегает случайного импорта кода из исходников.

Типичная ошибка: размещение кода в корне проекта без папки src. Это может привести к неоднозначности импортов при установке. Решение: всегда использовать явный каталог src или указывать package-dir в конфигурации.

Как настроить pyproject.toml для сборки?

Минимальный файл pyproject.toml:


[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my-library"
version = "0.1.0"
authors = [{ name = "Ваше Имя", email = "email@example.com" }]
description = "Краткое описание библиотеки"
readme = "README.md"
license = { text = "MIT" }
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
]
dependencies = [
    "requests>=2.28",
]

[project.urls]
Homepage = "https://github.com/username/my-library"

[tool.setuptools.packages.find]
where = ["src"]
  

Python написание библиотеки (создание собственной библиотеки python)

Проблема: несоответствие имени пакета (my-library vs my_library). PyPI требует, чтобы имя не содержало подчёркиваний, поэтому используйте дефисы. В коде же импортируется my_library. Решение: задать name с дефисом, а директорию назвать с подчёркиванием.

Как собрать и установить библиотеку локально?

  1. Установить сборщик: pip install build
  2. Собрать пакет: python -m build
  3. Установить локально: pip install dist/my_library-0.1.0-py3-none-any.whl

Для разработки удобно использовать pip install -e . (редактируемый режим). Это позволяет изменения в коде сразу видеть в импортах.

Ошибка: при pip install -e . может потребоваться явно указать --no-build-isolation при несовместимости версий setuptools. Решение: обновить инструменты и создать виртуальное окружение.

Как создать простую библиотеку без pyproject.toml (устаревший способ)?

Раньше использовали setup.py и setup.cfg. Пример setup.py:


from setuptools import setup, find_packages

setup(
    name='my-library',
    version='0.1.0',
    packages=find_packages(where='src'),
    package_dir={'': 'src'},
    install_requires=['requests'],
)
  

написать библиотеку на python (создание библиотеки на python)

Недостатки: меньше автоматизации, отсутствие единого стандарта, сложности с зависимостями для сборки.

Проблема: использование чистого setup.py может вызвать ошибки при запуске из-за исполнения произвольного кода. Современные инструменты (setuptools+pyproject.toml) этого лишены.

Как использовать Poetry для управления зависимостями и публикацией?

Poetry автоматически создаёт виртуальные окружения и упрощает процесс публикации. Инициализация:


poetry new my-library
cd my-library
poetry add requests
  

Структура включает pyproject.toml с секцией [tool.poetry]. Для публикации: poetry publish --build.

Ошибка: при конфликте версий Python и зависимостей Poetry может не установить пакет. Решение: точно указать версию Python в pyproject.toml.

Как создать namespace packages (пакеты с общим пространством имён)?

Например, два разных проекта company.a и company.b, каждый находится в своём репозитории. Структура:


company/
├── __init__.py  (можно оставить пустым или с комментарием)
├── a/
│   ├── __init__.py
│   └── module_a.py
  

В pyproject.toml каждого пакета указывается:


[tool.setuptools.packages.find]
where = ["src"]
namespaces = true
  

Такой подход хорош для крупных экосистем, где модули публикуются отдельно.

Типичная ошибка: забыть добавить файл __init__.py в промежуточные пакеты пространства имён. Без него импорт может не сработать.

Как добавить в библиотеку консольный интерфейс (CLI)?

Создайте внутри пакета файл cli.py с функцией main и укажите точку входа в pyproject.toml:


[project.scripts]
my_tool = "my_library.cli:main"
  

Теперь после установки будет доступна команда my_tool.

Цели каждого варианта: pyproject.toml – универсальность и соответствие стандартам; setup.py – поддержка старых проектов; Poetry – простота и автоматизация; namespace packages – модульная архитектура; CLI – удобство пользователя.

Расширенные примеры создания и публикации библиотеки

Пример 1. Полный пакет с несколькими модулями и тестами

Создадим библиотеку для работы с числами Фибоначчи.

Файловая структура:

Пример

fiblib/
├── pyproject.toml
├── README.md
├── src/
│   └── fiblib/
│       ├── __init__.py
│       ├── core.py
│       └── utils.py
└── tests/
    ├── __init__.py
    ├── test_core.py
    └── test_utils.py

Содержимое pyproject.toml:

Пример

[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "fiblib"
version = "0.2.0"
description = "Библиотека для работы с числами Фибоначчи"
readme = "README.md"
license = { text = "MIT" }
dependencies = []

[tool.setuptools.packages.find]
where = ["src"]

Файл src/fiblib/core.py:

Пример

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n: int) -> int:
    """Возвращает n-е число Фибоначчи."""
    if n < 0:
        raise ValueError("n must be non-negative")
    if n == 0:
        return 0
    if n == 1:
        return 1
    return fibonacci(n-1) + fibonacci(n-2)

Файл src/fiblib/utils.py:

Пример

def is_fibonacci(x: int) -> bool:
    """Проверяет, является ли число x числом Фибоначчи."""
    import math
    if x < 0:
        return False
    a = 5*x*x + 4
    b = 5*x*x - 4
    s1 = int(math.isqrt(a))
    s2 = int(math.isqrt(b))
    return s1*s1 == a or s2*s2 == b

Файл src/fiblib/__init__.py:

Пример

from .core import fibonacci
from .utils import is_fibonacci

Файл tests/test_core.py:

Пример

import pytest
from fiblib import fibonacci

def test_fibonacci_0():
    assert fibonacci(0) == 0

def test_fibonacci_1():
    assert fibonacci(1) == 1

def test_fibonacci_10():
    assert fibonacci(10) == 55

def test_fibonacci_negative():
    with pytest.raises(ValueError):
        fibonacci(-1)

Файл tests/test_utils.py:

Пример

from fiblib import is_fibonacci

def test_is_fibonacci_positive():
    assert is_fibonacci(21) == True
    assert is_fibonacci(22) == False

def test_is_fibonacci_zero():
    assert is_fibonacci(0) == True

def test_is_fibonacci_negative():
    assert is_fibonacci(-5) == False

Установка в редактируемом режиме и запуск тестов:

Пример

pip install -e .
pytest -v

Результат вывода pytest (усечённый):

============================= test session starts ==============================
collected 6 items

tests/test_core.py ....                                                   [ 66%]
tests/test_utils.py ..                                                    [100%]

============================== 6 passed in 0.12s ===============================

Пример 2. Публикация на PyPI с помощью twine

После сборки пакета (python -m build) создаются файлы в dist/. Загрузить их на PyPI можно через twine:

Пример

pip install twine
twine upload dist/*

При этом потребуется токен из аккаунта PyPI. Для тестирования используют TestPyPI: twine upload --repository testpypi dist/*.

Пример 3. Автоматизация версионирования с setuptools_scm

Чтобы версия бралась из Git-тегов, добавьте в pyproject.toml:

Пример

[build-system]
requires = ["setuptools>=68", "wheel", "setuptools_scm>=6"]

[project]
dynamic = ["version"]

[tool.setuptools_scm]

Теперь при каждом коммите с тегом вида v0.1.0 версия обновится автоматически. Без тега будет сформирована версия на основе dev.

Пример 4. Добавление документации с помощью Sphinx

Создайте папку docs/ и выполните:

Пример

sphinx-quickstart

Настройте docs/conf.py:

Пример

import os
import sys
sys.path.insert(0, os.path.abspath('../src'))

extensions = ['sphinx.ext.autodoc']

Сгенерируйте документацию по докстрингам:

Пример

sphinx-apidoc -o docs/source ../src/my_library
make html

Пример 5. Создание пакета с C-расширением через setuptools

Для производительности можно добавить модуль на Cython или C++. Пример с Cython:

Пример

# src/my_library/fast_math.pyx
def fast_square(int x):
    return x * x

В pyproject.toml укажите:

Пример

[build-system]
requires = ["setuptools", "cython", "wheel"]

[tool.setuptools]
package-data = {"my_library": ["*.so"]}

Сборка скомпилирует .so файл автоматически. Пример результата:

>>> from my_library.fast_math import fast_square
>>> fast_square(5)
25

Пример 6. Логирование и конфигурация в библиотеке

Добавьте в __init__.py:

Пример

import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())

Это позволяет пользователю настраивать логирование без появления лишних сообщений.

Пример 7. Обработка ошибок при несовместимости Python

В pyproject.toml укажите:

Пример

[project]
requires-python = ">=3.8"

Если пользователь попытается установить на более старую версию, pip выдаст ошибку совместимости.

Создание собственной библиотеки Python - comments

En
Python написание библиотеки (python)