обзор
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=’the.gigi@gmail.com’,
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 требует использования большого количества инструментов, указания большого количества метаданных и тщательного анализа ваших зависимостей и целевой аудитории. Но награда отличная.
Если вы напишите полезный код и правильно его упакуете, люди смогут легко установить его и получить от него пользу.