1. Code
  2. Python

Как написать свои собственные пакеты Python

Scroll to top

Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

Обзор

Python - замечательный язык программирования. Одной из самых слабых его сторон является пакеты. Это общеизвестный факт в сообществе. Установка, импорт, использование и создание пакетов улучшилась с годами, но она по-прежнему не соответствует новым языкам, таким как Go и Rust, которые могут многому научиться из-за проблем Python и других более зрелых языков.

В этом уроке вы узнаете все, что вам нужно знать, чтобы создавать и делиться своими собственными пакетами. Для общей информации о пакетах Python ознакомьтесь с разделом «Как использовать пакеты Python».

Упаковка проекта

Упаковка проекта - это процесс, с помощью которого вы берете надежно согласованный набор модулей Python и, возможно, других файлов и помещаете их в структуру, которую можно легко использовать. Существуют различные вещи, которые вы должны учитывать, такие как зависимости от других пакетов, внутренняя структура (подпакеты), управление версиями, целевая аудитория и форма пакета (исходный код и/или двоичный файл).

Пример

Начнем с быстрого примера. Пакет conman представляет собой пакет для управления конфигурацией. Он поддерживает несколько форматов файлов, а также распределенную конфигурацию с помощью etcd.

Содержимое пакета обычно хранится в одной директории (хотя обычно для разделения подпакетов в нескольких каталогах), а иногда, как и в этом случае, в собственном репозитории git.

Корневой каталог содержит различные файлы конфигурации (setup.py является обязательным и наиболее важным), а сам код пакета обычно находится в подкаталоге, имя которого является именем пакета и в идеале является каталогом тестов. Вот как это выглядит для «conman»:

1
> tree
2
3
.
4
5
├── LICENSE
6
7
├── MANIFEST.in
8
9
├── README.md
10
11
├── conman
12
13
│   ├── __init__.py
14
15
│   ├── __pycache__
16
17
│   ├── conman_base.py
18
19
│   ├── conman_etcd.py
20
21
│   └── conman_file.py
22
23
├── requirements.txt
24
25
├── setup.cfg
26
27
├── setup.py
28
29
├── test-requirements.txt
30
31
├── tests
32
33
│   ├── __pycache__
34
35
│   ├── conman_etcd_test.py
36
37
│   ├── conman_file_test.py
38
39
│   └── etcd_test_util.py
40
41
└── tox.ini

Давайте быстро заглянем в файл setup.py. Он импортирует две функции из пакета setuptools: setup() и find_packages(). Затем он вызывает функцию setup() и использует find_packages() для одного из параметров.

1
from setuptools import setup, find_packages
2
3
4
5
setup(name='conman',
6
7
      version='0.3',
8
9
      url='https://github.com/the-gigi/conman',
10
11
      license='MIT',
12
13
      author='Gigi Sayfan',
14
15
      author_email='the.gigi@gmail.com',
16
17
      description='Manage configuration files',
18
19
      packages=find_packages(exclude=['tests']),
20
21
      long_description=open('README.md').read(),
22
23
      zip_safe=False,
24
25
      setup_requires=['nose>=1.0'],
26
27
      test_suite='nose.collector')
28

Это довольно нормально. Хотя файл setup.py является обычным файлом Python, и вы можете делать все, что хотите, его основное задание - вызвать функцию setup() с соответствующими параметрами, поскольку он будет запускаться различными инструментами стандартным способом при установке вашего пакета. Я расскажу подробности в следующем разделе.

Файлы конфигурации

В дополнение к setup.py есть еще несколько дополнительных файлов конфигурации, которые могут отображаться здесь и обслуживать различные цели.

Setup.py

Функция setup() принимает большое количество именованных аргументов для управления многими аспектами установки пакета, а также для запуска различных команд. Многие аргументы указывают метаданные, используемые для поиска и фильтрации при загрузке вашего пакета в репозиторий.

  • name: имя вашего пакета (и как оно будет указано в PYPI)
  • version: это важно для поддержания правильного управления зависимостями
  • url: URL вашего пакета, обычно GitHub или URL-адрес readthedocs
  • packages: список подпакетов, которые необходимо включить; здесь помогает find_packages()
  • setup_requires: здесь вы указываете зависимости
  • test_suite: какой инструмент запускается во время тестирования

long_description устанавливается здесь на содержимое файла README.md, что является наилучшей практикой для использования одного источника правды.

setup.cfg

Файл setup.py также поддерживает интерфейс командной строки для запуска различных команд. Например, чтобы запустить модульные тесты, вы можете ввести: python setup.py test

1
running test

2


3
running egg_info
4
5
writing conman.egg-info/PKG-INFO
6
7
writing top-level names to conman.egg-info/top_level.txt
8
9
writing dependency_links to conman.egg-info/dependency_links.txt
10
11
reading manifest file 'conman.egg-info/SOURCES.txt'
12
13
reading manifest template 'MANIFEST.in'
14
15
writing manifest file 'conman.egg-info/SOURCES.txt'
16
17
running build_ext
18
19
test_add_bad_key (conman_etcd_test.ConManEtcdTest) ... ok
20
21
test_add_good_key (conman_etcd_test.ConManEtcdTest) ... ok
22
23
test_dictionary_access (conman_etcd_test.ConManEtcdTest) ... ok
24
25
test_initialization (conman_etcd_test.ConManEtcdTest) ... ok
26
27
test_refresh (conman_etcd_test.ConManEtcdTest) ... ok
28
29
test_add_config_file_from_env_var (conman_file_test.ConmanFileTest) ... ok
30
31
test_add_config_file_simple_guess_file_type (conman_file_test.ConmanFileTest) ... ok
32
33
test_add_config_file_simple_unknown_wrong_file_type (conman_file_test.ConmanFileTest) ... ok
34
35
test_add_config_file_simple_with_file_type (conman_file_test.ConmanFileTest) ... ok
36
37
test_add_config_file_simple_wrong_file_type (conman_file_test.ConmanFileTest) ... ok
38
39
test_add_config_file_with_base_dir (conman_file_test.ConmanFileTest) ... ok
40
41
test_dictionary_access (conman_file_test.ConmanFileTest) ... ok
42
43
test_guess_file_type (conman_file_test.ConmanFileTest) ... ok
44
45
test_init_no_files (conman_file_test.ConmanFileTest) ... ok
46
47
test_init_some_bad_files (conman_file_test.ConmanFileTest) ... ok
48
49
test_init_some_good_files (conman_file_test.ConmanFileTest) ... ok
50
51
52
53
----------------------------------------------------------------------
54
55
Ran 16 tests in 0.160s
56
57
58
59
OK
60

Файл setup.cfg является файлом формата ini, который может содержать параметры по умолчанию для команд, которые вы передаете setup.py. Здесь setup.cfg содержит некоторые опции для nosetests (наш тестовый раннер):

1
[nosetests]
2
3
verbose=1
4
5
nocapture=1

MANIFEST.in

Этот файл содержит файлы, которые не являются частью внутреннего каталога пакетов, но вы все еще хотите включить его. Обычно это файл readme, файл лицензи Важным файлом является файл requirements.txt. Этот файл используется pip для установки других необходимых пакетов.

Вот файл MANIFEST.in в conman:

1
include LICENSE
2
3
include README.md
4
5
include requirements.txt

Зависимости

Вы можете указать зависимости как в разделе install_requires файла setup.py, так и в файле requirements.txt. Pip установит автоматически зависимости от install_requires, но не из файла requirements.txt. Чтобы установить эти требования, вам нужно будет указать его явно при запуске pip install -r requirements.txt.

Параметр install_requires предназначен для указания минимальных и более абстрактных требований на основном уровне версии. Файл requirements.txt предназначен для более конкретных требований, часто с привязкой к второстепенным версиям.

Вот файл требований conman. Вы можете видеть, что все версии зафиксированы, что означает, что это может негативно повлиять, если один из этих пакетов обновится и внесет изменения, которые ломают conman.

1
PyYAML==3.11
2
3
python-etcd==0.4.3
4
5
urllib3==1.7
6
7
pyOpenSSL==0.15.1
8
9
psutil==4.0.0
10
11
six==1.7.3

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

Я изначально написал conman в 2014 году и не обращал на него особого внимания. Теперь для этого урока я обновил все, и для почти каждой зависимости были некоторые существенные улучшения.

Распределения

Вы можете создать дистрибутив источника или бинарный дистрибутив. Я расскажу об обоих.

Распределение источников

Вы создаете исходный дистрибутив с помощью команды: python setup.py sdist. Вот вывод для conman:

1
> python setup.py sdist
2
3
running sdist
4
5
running egg_info
6
7
writing conman.egg-info/PKG-INFO
8
9
writing top-level names to conman.egg-info/top_level.txt
10
11
writing dependency_links to conman.egg-info/dependency_links.txt
12
13
reading manifest file 'conman.egg-info/SOURCES.txt'
14
15
reading manifest template 'MANIFEST.in'
16
17
writing manifest file 'conman.egg-info/SOURCES.txt'
18
19
warning: sdist: standard file not found: should have one of README, README.rst, README.txt
20
21
22
23
running check
24
25
creating conman-0.3
26
27
creating conman-0.3/conman
28
29
creating conman-0.3/conman.egg-info
30
31
making hard links in conman-0.3...
32
33
hard linking LICENSE -> conman-0.3
34
35
hard linking MANIFEST.in -> conman-0.3
36
37
hard linking README.md -> conman-0.3
38
39
hard linking requirements.txt -> conman-0.3
40
41
hard linking setup.cfg -> conman-0.3
42
43
hard linking setup.py -> conman-0.3
44
45
hard linking conman/__init__.py -> conman-0.3/conman
46
47
hard linking conman/conman_base.py -> conman-0.3/conman
48
49
hard linking conman/conman_etcd.py -> conman-0.3/conman
50
51
hard linking conman/conman_file.py -> conman-0.3/conman
52
53
hard linking conman.egg-info/PKG-INFO -> conman-0.3/conman.egg-info
54
55
hard linking conman.egg-info/SOURCES.txt -> conman-0.3/conman.egg-info
56
57
hard linking conman.egg-info/dependency_links.txt -> conman-0.3/conman.egg-info
58
59
hard linking conman.egg-info/not-zip-safe -> conman-0.3/conman.egg-info
60
61
hard linking conman.egg-info/top_level.txt -> conman-0.3/conman.egg-info
62
63
copying setup.cfg -> conman-0.3
64
65
Writing conman-0.3/setup.cfg
66
67
creating dist
68
69
Creating tar archive
70
71
removing 'conman-0.3' (and everything under it)
72

Как вы можете видеть, я получил одно предупреждение о том, что отсутствует файл README с одним из стандартных префиксов, потому что мне нравится Markdown, поэтому вместо этого у меня есть «README.md». Кроме этого, все исходные файлы пакета были включены и дополнительные файлы. Затем в каталоге conman.egg-info была создана куча метаданных. Наконец, создается сжатый tar-архив под названием conman-0.3.tar.gz и помещается в подкаталог dist.

Для установки этого пакета потребуется шаг сборки (хотя это чистый Python). Вы можете установить его с помощью pip, просто передав путь к пакету. Например:

1
pip install dist/conman-0.3.tar.gz
2
3
Processing ./dist/conman-0.3.tar.gz
4
5
Installing collected packages: conman
6
7
  Running setup.py install for conman ... done

8


9
Successfully installed conman-0.3

Conman был установлен в site-packages и может быть импортирован как любой другой пакет:

1
import conman
2
3
conman.__file__
4
5
'/Users/gigi/.virtualenvs/conman/lib/python2.7/site-packages/conman/__init__.pyc'

Wheels

Wheels - относительно новый способ упаковки кода Python и, возможно, C-расширений. Они заменяют формат egg. Существует несколько типов колес: чистые колеса Python, платформы и универсальные колеса. Чистые колеса Python - это пакеты, такие как conman, у которых нет кода расширения C.

Платформенные колеса имеют код расширения C. Универсальные колеса - это чистые колеса Python, которые совместимы как с Python 2, так и с Python 3 с одинаковой базой кода (они не требуют даже 2to3). Если у вас есть чистый пакет Python и вы хотите, чтобы ваш пакет поддерживал как Python 2, так и Python 3 (становится все более и более важным), вы можете создать единую универсальную сборку вместо одного колеса для Python 2 и одного колеса для Python 3.

Если в вашем пакете есть код расширения C, вы должны создать колесо платформы для каждой платформы. Огромное преимущество колес, особенно для пакетов с расширениями C, заключается в том, что нет необходимости иметь компилятор и поддерживающие библиотеки, доступные на целевой машине. Колесо уже содержит встроенный пакет. Таким образом, вы знаете, что его гораздо быстрее установить, потому что это буквально просто копия. Люди, которые используют научные библиотеки, такие как Numpy и Pandas, действительно могут это оценить, поскольку установка таких пакетов требует много времени и может быть неудачной, если какая-то библиотека отсутствует или компилятор не настроен должным образом.

Команда для создания чистых или платформенных колес: python setup.py bdist_wheel.

Setuptools - движок, который обеспечивает функцию setup(), будет автоматически обнаруживаться, если требуется чистое или платформенное колесо.

1
running bdist_wheel
2
3
running build
4
5
running build_py
6
7
creating build
8
9
creating build/lib
10
11
creating build/lib/conman
12
13
copying conman/__init__.py -> build/lib/conman
14
15
copying conman/conman_base.py -> build/lib/conman
16
17
copying conman/conman_etcd.py -> build/lib/conman
18
19
copying conman/conman_file.py -> build/lib/conman
20
21
installing to build/bdist.macosx-10.9-x86_64/wheel
22
23
running install

24


25
running install_lib
26
27
creating build/bdist.macosx-10.9-x86_64
28
29
creating build/bdist.macosx-10.9-x86_64/wheel
30
31
creating build/bdist.macosx-10.9-x86_64/wheel/conman
32
33
copying build/lib/conman/__init__.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
34
35
copying build/lib/conman/conman_base.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
36
37
copying build/lib/conman/conman_etcd.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
38
39
copying build/lib/conman/conman_file.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
40
41
running install_egg_info
42
43
running egg_info
44
45
creating conman.egg-info
46
47
writing conman.egg-info/PKG-INFO
48
49
writing top-level names to conman.egg-info/top_level.txt
50
51
writing dependency_links to conman.egg-info/dependency_links.txt
52
53
writing manifest file 'conman.egg-info/SOURCES.txt'
54
55
reading manifest file 'conman.egg-info/SOURCES.txt'
56
57
reading manifest template 'MANIFEST.in'
58
59
writing manifest file 'conman.egg-info/SOURCES.txt'
60
61
Copying conman.egg-info to build/bdist.macosx-10.9-x86_64/wheel/conman-0.3-py2.7.egg-info
62
63
running install_scripts
64
65
creating build/bdist.macosx-10.9-x86_64/wheel/conman-0.3.dist-info/WHEEL

При проверке каталога dist вы можете увидеть, что было создано чистое колесо Python:

1
ls -la dist
2
3
4
5
dist/
6
7
total 32
8
9
-rw-r--r--  1 gigi  staff   5.5K Feb 29 07:57 conman-0.3-py2-none-any.whl
10
11
-rw-r--r--  1 gigi  staff   4.4K Feb 28 23:33 conman-0.3.tar.gz

Название «conman-0.3-py2-none-any.whl» имеет несколько компонентов: имя пакета, версию пакета, версию Python, версию платформы и, наконец, расширение «whl».

Чтобы создавать универсальные пакеты, вы просто добавляете --universal, как в python setup.py bdist_wheel --universal.

Полученное колесо называется "conman-0.3-py2.py3-none-any.whl".

Обратите внимание, что вы несете ответственность за то, чтобы ваш код действительно работал как с Python 2, так и с Python 3, если вы создаете универсальный пакет.

Заключение

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

Если вы правильно напишите полезный код и упакуете его, люди смогут легко его установить и извлечь из него выгоду.