обзор
Python — замечательный язык программирования и многое другое. Одним из его слабых мест является упаковка. Это общеизвестный факт в сообществе. Установка, импорт, использование и создание пакетов с годами улучшились, но они все еще не соответствуют новым языкам, таким как Go и Rust, которые могли бы многому научиться из борьбы Python и других более зрелых языков.
В этом уроке вы узнаете все, что вам нужно знать, чтобы создавать свои собственные пакеты и делиться ими. Общие сведения о пакетах Python см. В разделе « Как использовать пакеты Python» .
Упаковка проекта
Упаковка проекта — это процесс, с помощью которого вы берете согласованный набор модулей Python и, возможно, другие файлы и помещаете их в структуру, которую можно легко использовать. Есть несколько вещей, которые вы должны учитывать, такие как зависимости от других пакетов, внутренняя структура (подпакеты), управление версиями, целевая аудитория и форма пакета (исходный и / или двоичный).
пример
Давайте начнем с быстрого примера. Пакет conman — это пакет для управления конфигурацией. Он поддерживает несколько форматов файлов, а также распределенную конфигурацию с использованием etcd .
Содержимое пакета, как правило, хранится в одном каталоге (хотя распространено разделение подпакетов в нескольких каталогах), а иногда, как в этом случае, в его собственном git-репозитории.
Корневой каталог содержит различные файлы конфигурации ( setup.py
является обязательным и самым важным), а сам код пакета обычно находится в подкаталоге, имя которого является именем пакета и, в идеале, каталогом тестов. Вот как это выглядит для «conman»:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
> tree
.
├── LICENSE
├── MANIFEST.in
├── README.md
├── conman
│ ├── __init__.py
│ ├── __pycache__
│ ├── conman_base.py
│ ├── conman_etcd.py
│ └── conman_file.py
├── requirements.txt
├── setup.cfg
├── setup.py
├── test-requirements.txt
├── tests
│ ├── __pycache__
│ ├── conman_etcd_test.py
│ ├── conman_file_test.py
│ └── etcd_test_util.py
└── tox.ini
|
Давайте быстро взглянем на файл setup.py
. Он импортирует две функции из пакета setuptools : setup()
и find_packages()
. Затем он вызывает функцию setup()
и использует find_packages()
для одного из параметров.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
from setuptools import setup, find_packages
setup(name=’conman’,
version=’0.3′,
url=’https://github.com/the-gigi/conman’,
license=’MIT’,
author=’Gigi Sayfan’,
author_email=’[email protected]’,
description=’Manage configuration files’,
packages=find_packages(exclude=[‘tests’]),
long_description=open(‘README.md’).read(),
zip_safe=False,
setup_requires=[‘nose>=1.0’],
test_suite=’nose.collector’)
|
Это довольно нормально. Хотя файл setup.py
является обычным файлом Python, и вы можете в нем делать все, что захотите, его основная задача — вызывать функцию setup()
с соответствующими параметрами, поскольку при установке он будет вызываться различными инструментами стандартным способом. ваш пакет Я расскажу подробности в следующем разделе.
Файлы конфигурации
В дополнение к setup.py
, есть несколько других необязательных файлов конфигурации, которые могут отображаться здесь и использоваться для различных целей.
Setup.py
Функция setup()
принимает большое количество именованных аргументов для управления многими аспектами установки пакета, а также выполнения различных команд. Многие аргументы указывают метаданные, используемые для поиска и фильтрации при загрузке вашего пакета в хранилище.
- name: название вашего пакета (и как оно будет указано в PYPI)
- версия: это важно для правильного управления зависимостями
- url: URL вашего пакета, обычно GitHub или, возможно, URL readthedocs
- пакеты: список подпакетов, которые должны быть включены;
find_packages()
помогает здесь - setup_requires: здесь вы указываете зависимости
- test_suite: какой инструмент запустить во время теста
long_description
задается здесь как содержимое файла README.md
, что является наилучшей практикой, чтобы иметь единый источник правды.
setup.cfg
Файл setup.py также служит интерфейсом командной строки для запуска различных команд. Например, чтобы запустить модульные тесты, вы можете набрать: python setup.py test
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
running test
running egg_info
writing conman.egg-info/PKG-INFO
writing top-level names to conman.egg-info/top_level.txt
writing dependency_links to conman.egg-info/dependency_links.txt
reading manifest file ‘conman.egg-info/SOURCES.txt’
reading manifest template ‘MANIFEST.in’
writing manifest file ‘conman.egg-info/SOURCES.txt’
running build_ext
test_add_bad_key (conman_etcd_test.ConManEtcdTest) … ok
test_add_good_key (conman_etcd_test.ConManEtcdTest) … ok
test_dictionary_access (conman_etcd_test.ConManEtcdTest) … ok
test_initialization (conman_etcd_test.ConManEtcdTest) … ok
test_refresh (conman_etcd_test.ConManEtcdTest) … ok
test_add_config_file_from_env_var (conman_file_test.ConmanFileTest) … ok
test_add_config_file_simple_guess_file_type (conman_file_test.ConmanFileTest) … ok
test_add_config_file_simple_unknown_wrong_file_type (conman_file_test.ConmanFileTest) … ok
test_add_config_file_simple_with_file_type (conman_file_test.ConmanFileTest) … ok
test_add_config_file_simple_wrong_file_type (conman_file_test.ConmanFileTest) … ok
test_add_config_file_with_base_dir (conman_file_test.ConmanFileTest) … ok
test_dictionary_access (conman_file_test.ConmanFileTest) … ok
test_guess_file_type (conman_file_test.ConmanFileTest) … ok
test_init_no_files (conman_file_test.ConmanFileTest) … ok
test_init_some_bad_files (conman_file_test.ConmanFileTest) … ok
test_init_some_good_files (conman_file_test.ConmanFileTest) … ok
———————————————————————-
Ran 16 tests in 0.160s
OK
|
Setup.cfg — это файл в формате ini, который может содержать значения по умолчанию для команд, которые вы передаете в setup.py
. Здесь setup.cfg содержит несколько опций для nosetests
(наш тестовый бегун):
1
2
3
4
5
|
[nosetests]
verbose=1
nocapture=1
|
MANIFEST.in
Этот файл содержит файлы, которые не являются частью внутренней директории пакета, но вы все еще хотите включить. Это, как правило, файл readme
файл лицензии и тому подобное. Важным файлом является файл requirements.txt
. Этот файл используется pip для установки других необходимых пакетов.
Вот файл MANIFEST.in
Конмана:
1
2
3
4
5
|
include LICENSE
include README.md
include requirements.txt
|
зависимости
Вы можете указать зависимости как в разделе install_requires
файла setup.py
и в файле require.txt. Pip автоматически установит зависимости из install_requires
, но не из файла needs.txt. Чтобы установить эти требования, вы должны будете явно указать их при запуске pip: pip install -r requirements.txt
.
Опция install_requires
предназначена для указания минимальных и более абстрактных требований на уровне основной версии. Файл needs.txt предназначен для более конкретных требований, часто с закрепленными второстепенными версиями.
Вот файл требований conman. Вы можете видеть, что все версии закреплены, что означает, что на него может быть оказано негативное влияние, если один из этих пакетов обновится и внесет изменение, нарушающее конман.
01
02
03
04
05
06
07
08
09
10
11
|
PyYAML==3.11
python-etcd==0.4.3
urllib3==1.7
pyOpenSSL==0.15.1
psutil==4.0.0
six==1.7.3
|
Закрепление дает вам предсказуемость и душевное спокойствие. Это особенно важно, если многие люди устанавливают ваш пакет в разное время. Без закрепления, каждый человек получит различное сочетание версий зависимостей в зависимости от того, когда они его установили. Недостатком закрепления является то, что если вы не успеваете за развитием зависимостей, вы можете застрять в старой, плохо работающей и даже уязвимой версии зависимости.
Первоначально я написал conman в 2014 году и не обращал на это особого внимания. Теперь, для этого урока, я обновил все, и было несколько серьезных улучшений для почти каждой зависимости.
Распределения
Вы можете создать исходный дистрибутив или бинарный дистрибутив. Я покрою оба.
Распределение источников
Вы создаете исходный дистрибутив с помощью команды: python setup.py sdist
. Вот вывод для conman:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
> python setup.py sdist
running sdist
running egg_info
writing conman.egg-info/PKG-INFO
writing top-level names to conman.egg-info/top_level.txt
writing dependency_links to conman.egg-info/dependency_links.txt
reading manifest file ‘conman.egg-info/SOURCES.txt’
reading manifest template ‘MANIFEST.in’
writing manifest file ‘conman.egg-info/SOURCES.txt’
warning: sdist: standard file not found: should have one of README, README.rst, README.txt
running check
creating conman-0.3
creating conman-0.3/conman
creating conman-0.3/conman.egg-info
making hard links in conman-0.3…
hard linking LICENSE -> conman-0.3
hard linking MANIFEST.in -> conman-0.3
hard linking README.md -> conman-0.3
hard linking requirements.txt -> conman-0.3
hard linking setup.cfg -> conman-0.3
hard linking setup.py -> conman-0.3
hard linking conman/__init__.py -> conman-0.3/conman
hard linking conman/conman_base.py -> conman-0.3/conman
hard linking conman/conman_etcd.py -> conman-0.3/conman
hard linking conman/conman_file.py -> conman-0.3/conman
hard linking conman.egg-info/PKG-INFO -> conman-0.3/conman.egg-info
hard linking conman.egg-info/SOURCES.txt -> conman-0.3/conman.egg-info
hard linking conman.egg-info/dependency_links.txt -> conman-0.3/conman.egg-info
hard linking conman.egg-info/not-zip-safe -> conman-0.3/conman.egg-info
hard linking conman.egg-info/top_level.txt -> conman-0.3/conman.egg-info
copying setup.cfg -> conman-0.3
Writing conman-0.3/setup.cfg
creating dist
Creating tar archive
removing ‘conman-0.3’ (and everything under it)
|
Как вы можете видеть, я получил одно предупреждение об отсутствии файла README с одним из стандартных префиксов, потому что мне нравится Markdown, поэтому вместо этого у меня есть «README.md». Кроме этого, все исходные файлы пакета были включены и дополнительные файлы. Затем в conman.egg-info
была создана группа метаданных. Наконец, создается сжатый tar-архив с именем conman-0.3.tar.gz
который помещается в подкаталог dist
.
Для установки этого пакета потребуется этап сборки (даже если это чистый Python). Вы можете установить его с помощью pip как обычно, просто передав путь к пакету. Например:
1
2
3
4
5
6
7
8
9
|
pip install dist/conman-0.3.tar.gz
Processing ./dist/conman-0.3.tar.gz
Installing collected packages: conman
Running setup.py install for conman … done
Successfully installed conman-0.3
|
Conman был установлен в site-пакеты и может быть импортирован как любой другой пакет:
1
2
3
4
5
|
import conman
conman.__file__
‘/Users/gigi/.virtualenvs/conman/lib/python2.7/site-packages/conman/__init__.pyc’
|
Колеса
Колеса — это относительно новый способ упаковки кода Python и, возможно, расширений C. Они заменяют формат яйца. Существует несколько типов колес: колеса чистого 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()
— автоматически обнаружит, нужно ли чистое колесо или колесо платформы.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
running bdist_wheel
running build
running build_py
creating build
creating build/lib
creating build/lib/conman
copying conman/__init__.py -> build/lib/conman
copying conman/conman_base.py -> build/lib/conman
copying conman/conman_etcd.py -> build/lib/conman
copying conman/conman_file.py -> build/lib/conman
installing to build/bdist.macosx-10.9-x86_64/wheel
running install
running install_lib
creating build/bdist.macosx-10.9-x86_64
creating build/bdist.macosx-10.9-x86_64/wheel
creating build/bdist.macosx-10.9-x86_64/wheel/conman
copying build/lib/conman/__init__.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
copying build/lib/conman/conman_base.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
copying build/lib/conman/conman_etcd.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
copying build/lib/conman/conman_file.py -> build/bdist.macosx-10.9-x86_64/wheel/conman
running install_egg_info
running egg_info
creating conman.egg-info
writing conman.egg-info/PKG-INFO
writing top-level names to conman.egg-info/top_level.txt
writing dependency_links to conman.egg-info/dependency_links.txt
writing manifest file ‘conman.egg-info/SOURCES.txt’
reading manifest file ‘conman.egg-info/SOURCES.txt’
reading manifest template ‘MANIFEST.in’
writing manifest file ‘conman.egg-info/SOURCES.txt’
Copying conman.egg-info to build/bdist.macosx-10.9-x86_64/wheel/conman-0.3-py2.7.egg-info
running install_scripts
creating build/bdist.macosx-10.9-x86_64/wheel/conman-0.3.dist-info/WHEEL
|
Проверяя каталог dist
, вы можете увидеть, что было создано чистое колесо Python:
01
02
03
04
05
06
07
08
09
10
11
|
ls -la dist
dist/
total 32
-rw-r—r— 1 gigi staff 5.5K Feb 29 07:57 conman-0.3-py2-none-any.whl
-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 требует использования большого количества инструментов, указания большого количества метаданных и тщательного анализа ваших зависимостей и целевой аудитории. Но награда отличная.
Если вы напишите полезный код и правильно его упакуете, люди смогут легко установить его и получить от него пользу.