Как написать, упаковать и распространить библиотеку на Python
Python — отличный язык программирования, но упаковка — одно из его самых слабых мест. Это общеизвестный факт в обществе. Установка, импорт, использование и создание пакетов значительно улучшились за эти годы, но они все еще не соответствуют новым языкам, таким как Go и Rust, которые многому научились в борьбе с Python и другими зрелыми языками.
В этом руководстве вы узнаете все, что вам нужно знать о написании, упаковке и распространении собственных пакетов.
Как написать библиотеку Python
В Python 3 есть отличный объект Path
, который является огромным улучшением по сравнению с неудобным модулем os.path
в Python 2. Но ему не хватает одной важной возможности — поиска пути к текущему сценарию. Это очень важно, если вы хотите найти файлы доступа относительно текущего скрипта.
Во многих случаях сценарий можно установить в любом месте, поэтому вы не можете использовать абсолютные пути, а в качестве рабочего каталога можно указать любое значение, поэтому вы не можете использовать относительный путь. Если вы хотите получить доступ к файлу в подкаталоге или родительском каталоге, вы должны иметь возможность определить текущий каталог сценария.
Вот как вы это делаете в Python:
import pathlib
script_dir = pathlib.Path(__file__).parent.resolve()
Чтобы получить доступ к файлу с именем «file.txt» в подкаталоге «data» каталога текущего скрипта, вы можете использовать следующий код:
print(open(str(script_dir/'data/file.txt').read())
В пакете pathology у вас есть встроенный метод script_dir, и вы используете его следующим образом:
from pathology.Path import script_dir
print(open(str(script_dir()/'data/file.txt').read())
Да, это полный рот. Пакет патологии очень прост. Он наследует свой собственный класс Path от Pathlib библиотеки path и добавляет статический script_dir(), который всегда возвращает путь вызывающего скрипта.
Вот реализация:
import pathlib
import inspect
class Path(type(pathlib.Path())):
@staticmethod
def script_dir():
print(inspect.stack()[1].filename)
p = pathlib.Path(inspect.stack()[1].filename)
return p.parent.resolve()
Из-за кросс-платформенной реализации pathlib.Path
вы можете наследовать непосредственно от него и должны наследовать от определенного подкласса (PosixPath
или WindowsPath
). Разрешение script_dir
использует модуль проверки, чтобы найти вызывающую программу, а затем ее атрибут имени файла.
Более 2 миллионов тем и плагинов WordPress, веб-шаблонов и шаблонов электронной почты, наборов пользовательского интерфейса и многого другого
Загрузите тысячи тем и плагинов WordPress, веб-шаблонов, элементов пользовательского интерфейса и многое другое с членством в Envato Elements. Получите неограниченный доступ к растущей библиотеке из миллионов творческих и кодовых ресурсов.
Тестирование пакета патологии
import os
import shutil
from unittest import TestCase
from pathology.path import Path
class PathTest(TestCase):
def test_script_dir(self):
expected = os.path.abspath(os.path.dirname(__file__))
actual = str(Path.script_dir())
self.assertEqual(expected, actual)
def test_file_access(self):
script_dir = os.path.abspath(os.path.dirname(__file__))
subdir = os.path.join(script_dir, 'test_data')
if Path(subdir).is_dir():
shutil.rmtree(subdir)
os.makedirs(subdir)
file_path = str(Path(subdir)/'file.txt')
content = '123'
open(file_path, 'w').write(content)
test_path = Path.script_dir()/subdir/'file.txt'
actual = open(str(test_path)).read()
self.assertEqual(content, actual)
Путь Python
Пакеты Python должны быть установлены где-то на пути поиска Python, чтобы их можно было импортировать модулями Python. Путь поиска Python представляет собой список каталогов и всегда доступен в sys.path
. Вот мой текущий sys.path
:
>>> print('\n'.join(sys.path))
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python36.zip
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/lib-dynload
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/site-packages
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/site-packages/setuptools-27.2.0-py3.6.egg
Обратите внимание, что первая пустая строка вывода представляет текущий каталог, поэтому вы можете импортировать модули из текущего рабочего каталога, каким бы он ни был. Вы можете напрямую добавлять или удалять каталоги в/из sys.path.
Вы также можете определить переменную среды PYTHONPATH
и несколько других способов управления ею. Стандартные site-packages
включены по умолчанию, и именно сюда идут пакеты, которые вы устанавливаете с помощью pip.
Как упаковать библиотеку Python
Теперь, когда у нас есть код и тесты, давайте упакуем их в соответствующую библиотеку. Python предоставляет простой способ через модуль установки. Вы создаете файл с именем setup.py в корневом каталоге вашего пакета.
Файлы setup.py
содержат множество метаданных, таких как автор, лицензия, сопровождающие и другую информацию о пакете. Это в дополнение к элементу пакетов (packages
), который использует функцию find_packages()
, импортированную из setuptools
, для поиска подпакетов.
Вот файл setup.py пакета патологии:
from setuptools import setup, find_packages
setup(name='pathology',
version='0.1',
url='https://github.com/the-gigi/pathology',
license='MIT',
author='Gigi Sayfan',
author_email='the.gigi@gmail.com',
description='Add static script_dir() method to Path',
packages=find_packages(exclude=['tests']),
long_description=open('README.md').read(),
zip_safe=False)
Исходный дистрибутив
Пакет исходного дистрибутива — это архивный файл, содержащий пакеты Python, модули, а также другие файлы, которые используются для выпуска пакета (например, версии 1, 2 и т. д.). После распространения файла конечные пользователи могут загрузить и установить его в своей операционной системе.
Чтобы создать исходный дистрибутив (sdist), запустите: python setup.py sdist
Давайте создадим исходный дистрибутив:
$ python setup.py sdist
running sdist
running egg_info
creating pathology.egg-info
writing pathology.egg-info/PKG-INFO
writing dependency_links to pathology.egg-info/dependency_links.txt
writing top-level names to pathology.egg-info/top_level.txt
writing manifest file 'pathology.egg-info/SOURCES.txt'
reading manifest file 'pathology.egg-info/SOURCES.txt'
writing manifest file 'pathology.egg-info/SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt
running check
creating pathology-0.1
creating pathology-0.1/pathology
creating pathology-0.1/pathology.egg-info
copying files to pathology-0.1...
copying setup.py -> pathology-0.1
copying pathology/__init__.py -> pathology-0.1/pathology
copying pathology/path.py -> pathology-0.1/pathology
copying pathology.egg-info/PKG-INFO -> pathology-0.1/pathology.egg-info
copying pathology.egg-info/SOURCES.txt -> pathology-0.1/pathology.egg-info
copying pathology.egg-info/dependency_links.txt -> pathology-0.1/pathology.egg-info
copying pathology.egg-info/not-zip-safe -> pathology-0.1/pathology.egg-info
copying pathology.egg-info/top_level.txt -> pathology-0.1/pathology.egg-info
Writing pathology-0.1/setup.cfg
creating dist
Creating tar archive
removing 'pathology-0.1' (and everything under it)
Предупреждение связано с тем, что я использовал нестандартный файл README.md. Безопасно игнорировать. Приведенная выше команда создаст файл архива в формате по умолчанию для текущей операционной системы. Для систем Unix будет сгенерирован сжатый tar-файл в каталоге dist:
$ ls -la dist
total 8
drwxr-xr-x 3 gigi.sayfan gigi.sayfan 102 Apr 18 21:20 .
drwxr-xr-x 12 gigi.sayfan gigi.sayfan 408 Apr 18 21:20 ..
-rw-r--r-- 1 gigi.sayfan gigi.sayfan 1223 Apr 18 21:20 pathology-0.1.tar.gz
Если вы используете Windows, создается zip-файл.
Вы также можете указать другие дополнительные форматы файлов, используя параметр формата, как показано ниже.
python setup.py sdist --formats=gztar,zip
Например, приведенная выше команда создаст tar-архив, сжатый gzip, и zip-файл.
Доступны следующие форматы:
zip
: .zip
gztar
: .tar.gz
bztar
: .tar.bz2
xztar
: .tar.xz
ztar
: .tar.Z
tar
: .tar
Бинарное распространение
Чтобы создать двоичный дистрибутив, называемый колесом, запустите: python setup.py bdist_wheel
$ python setup.py bdist_wheel
running bdist_wheel
running build
running build_py
creating build
creating build/lib
creating build/lib/pathology
copying pathology/__init__.py -> build/lib/pathology
copying pathology/path.py -> build/lib/pathology
installing to build/bdist.macosx-10.7-x86_64/wheel
running install
running install_lib
creating build/bdist.macosx-10.7-x86_64
creating build/bdist.macosx-10.7-x86_64/wheel
creating build/bdist.macosx-10.7-x86_64/wheel/pathology
copying build/lib/pathology/__init__.py -> build/bdist.macosx-10.7-x86_64/wheel/pathology
copying build/lib/pathology/path.py -> build/bdist.macosx-10.7-x86_64/wheel/pathology
running install_egg_info
running egg_info
writing pathology.egg-info/PKG-INFO
writing dependency_links to pathology.egg-info/dependency_links.txt
writing top-level names to pathology.egg-info/top_level.txt
reading manifest file 'pathology.egg-info/SOURCES.txt'
writing manifest file 'pathology.egg-info/SOURCES.txt'
Copying pathology.egg-info to build/bdist.macosx-10.7-x86_64/wheel/pathology-0.1-py3.6.egg-info
running install_scripts
creating build/bdist.macosx-10.7-x86_64/wheel/pathology-0.1.dist-info/WHEEL
Пакет патологии содержит только чистые модули Python, поэтому можно создать универсальный пакет. Если ваш пакет включает расширения C, вам придется создать отдельное колесо для каждой платформы:Пакет патологии содержит только чистые модули Python, поэтому можно создать универсальный пакет. Если ваш пакет включает расширения C, вам придется создать отдельное колесо для каждой платформы:
$ ls -la dist
total 16
drwxr-xr-x 4 gigi.sayfan gigi.sayfan 136 Apr 18 21:24 .
drwxr-xr-x 13 gigi.sayfan gigi.sayfan 442 Apr 18 21:24 ..
-rw-r--r-- 1 gigi.sayfan gigi.sayfan 2695 Apr 18 21:24 pathology-0.1-py3-none-any.whl
-rw-r--r-- 1 gigi.sayfan gigi.sayfan 1223 Apr 18 21:20 pathology-0.1.tar.gz
Чтобы глубже погрузиться в тему упаковки библиотек Python, можно ознакомиться информацией как писать собственные пакеты Python.
Как распространять пакет Python
Python имеет центральный репозиторий пакетов под названием PyPI (Python Packages Index). PyPI упрощает управление различными версиями пакетов. Например, если пользователю нужно установить определенную версию пакета, pip знает, где ее искать.
Когда вы устанавливаете пакет Python с помощью pip, он загружает пакет из PyPI (если вы не укажете другой репозиторий). Чтобы распространить наш пакет патологии, нам нужно загрузить его в PyPI и предоставить некоторые дополнительные метаданные, которые требуются PyPI. Шаги:
- Обновите версию вашего пипса.
- Создайте учетную запись на PyPI (только один раз).
- Зарегистрируйте свой пакет.
- Загрузите свой пакет.
Обновите версию вашего pip
Убедитесь, что в вашей операционной системе установлена последняя версия pip. Чтобы обновить pip, введите следующую команду
python3 -m pip install --upgrade pip
Завести аккаунт
Вы можете создать учетную запись на веб-сайте PyPI. Затем создайте файл .pypirc в своем домашнем каталоге:
[distutils]
index-servers=pypi
[pypi]
repository = https://pypi.python.org/pypi
username = the_gigi
В целях тестирования вы можете добавить индексный сервер pypitest в свой файл .pypirc:
[distutils]
index-servers=
pypi
pypitest
[pypitest]
repository = https://testpypi.python.org/pypi
username = the_gigi
[pypi]
repository = https://pypi.python.org/pypi
username = the_gigi
Зарегистрируйте свой пакет
Если это первый выпуск вашего пакета, вам необходимо зарегистрировать его в PyPI. Используйте команду регистрации файла setup.py. Он попросит вас ввести пароль. Обратите внимание, что я указываю на тестовый репозиторий здесь:
$ python setup.py register -r pypitest
running register
running egg_info
writing pathology.egg-info/PKG-INFO
writing dependency_links to pathology.egg-info/dependency_links.txt
writing top-level names to pathology.egg-info/top_level.txt
reading manifest file 'pathology.egg-info/SOURCES.txt'
writing manifest file 'pathology.egg-info/SOURCES.txt'
running check
Password:
Registering pathology to https://testpypi.python.org/pypi
Server response (200): OK
Загрузите свой пакет
Теперь, когда пакет зарегистрирован, мы можем его загрузить. Я рекомендую использовать twine, который более надежен. Установите его как обычно, используя pip install twine
. Затем загрузите свой пакет с помощью twine и укажите свой пароль (отредактировано ниже):
$ twine upload -r pypitest -p <redacted> dist/*
Uploading distributions to https://testpypi.python.org/pypi
Uploading pathology-0.1-py3-none-any.whl
[================================] 5679/5679 - 00:00:02
Uploading pathology-0.1.tar.gz
[================================] 4185/4185 - 00:00:01
Пакет теперь доступен на официальном сайте Pypi, как показано ниже.
Чтобы установить его с помощью pip, просто введите следующую команду:
pip install pathology
Чтобы глубже погрузиться в тему распространения ваших пакетов, необходимо ознакомьться с информацией как поделиться своими пакетами Python.
Вывод
В этом руководстве мы прошли полноценный процесс написания библиотеки Python, ее упаковки и распространения через PyPI. К этому моменту у вас уже должны быть все инструменты для создания библиотек и обмена ими со всем миром.