Ликбез по пакетам и шпаргалка по модулям в Python

Как вы, возможно знаете, код на Python хранится в модулях (modules), которые могут быть объединены в пакеты (packages). Это руководство призвано подробно рассказать именно о пакетах, однако совсем не упомянуть модули нельзя, поэтому я немного расскажу и о них. Многое из того, что применимо к модулям, справедливо и для пакетов, особенно если принять во внимание тот факт, что каждый, как правило, ведёт себя как модуль.
Кратко о модулях
Модуль в Python — это файл с кодом. Во время же исполнения модуль представлен соответствующим объектом, атрибутами которого являются:
- Объявления, присутствующие в файле.
- Объекты, импортированные в этот модуль откуда-либо.
При этом определения и импортированные сущности ничем друг от друга не отличаются: и то, и другое — это всего лишь именованные ссылки на некоторые объекты первого класса (такие, которые могут быть переданы из одного участка кода в другой как обычные значения).
Такое единообразие удобно, например, при рефакторинге: мы можем разделить один разросшийся модуль на несколько, а потом импортировать вынесенные определения в оригинальный модуль. При этом с точки зрения внешнего наблюдателя переработанный модуль будет иметь те же атрибуты, которые имел до внесения изменений, а значит у пользователей модуля ничего в коде не сломается.
Модули и видимость содержимого
В Python нет настоящего сокрытия атрибутов объектов, поэтому и атрибуты объекта модуля так или иначе всегда доступны после импорта последнего. Однако существует ряд соглашений, которые влияют на процесс импортирования и поведение инструментов, работающих с кодом.
Так атрибуты, имя которых начинается с одиночного подчёркивания, считаются как бы помеченными «для внутреннего использования», и обычно не отображаются в IDE при обращению к объекту «через точку». И linter обычно предупреждает об использовании таких атрибутов, мол, «небезопасно!». «Опасность» состоит в том, что автор кода имеет полное право изменять состав таких атрибутов без уведомления пользователей кода. Поэтому программист, использовавший в своём коде приватные части чужого кода рискует в какой-то момент получить код, который перестанет работать при обновлении сторонней библиотеки.
Итак, мы можем определять публичные атрибуты модуля, приватные атрибуты (так называют упомянутые выше атрибуты «для внутреннего пользования»). И данное разделение касается не только определений, содержащихся в самом модуле, но и импортируемых сущностей. Ведь все импортированные объекты становятся атрибутами и того модуля, в который они импортированы.
Есть и третья группа атрибутов — атрибуты, добавляемые в область видимости при импортировании всего содержимого модуля («со звёздочкой», from module import * ). Если ничего явно не указывать, то при таком импортировании в текущую область видимости добавятся все публичные атрибуты модуля. Помимо данного умолчания существует и возможность явно указать, что конкретно будет экспортировано при импорте со звёздочкой. Для управления названным методом импорта существует атрибут __all__ , в который можно положить список (а ещё лучше — кортеж) строк с именами, которые будут экспортироваться.
Живой пример видимости атрибутов модулей.
Рассмотрим пример, демонстрирующий всё вышеописанное. Пусть у нас будет два файла:
# Файл "module.py" from other_module import CAT, DOG as _DOG, _GOAT FISH = 'fish' MEAT = 'meat' _CARROT = 'carrot' __all__ = ('FISH', '_CARROT')
# Файл "other_module.py" CAT = 'cat' DOG = 'dog' _GOAT = 'goat'
Рассмотрим сначала обычный импорт import module . Если импортировать модуль таким образом, то IDE, REPL и остальные инструменты «увидят» у модуля следующие атрибуты:
- FISH , MEAT т.к. имена констант — публичные,
- CAT , т.к. константа импортирована под публичным именем.
А эти атрибуты не будут видны:
- _DOG , т.к. при импортировании константа переименована в приватной манере,
- _GOAT , т.к. импортирована по своему приватному имени (тут линтер может и поругать за обращение к приватному атрибуту модуля!),
- _CARROT , ибо приватная константа.
Импорт import other_module я не рассматриваю как тривиальный случай.
Теперь рассмотрим импорт всего содержимого module:
from module import *
После импортирования в текущей области видимости мы получим ровно два новых имени: FISH и _CARROT — именно они перечислены в атрибуте __all__ . Заметьте, что в данном случае при массовом импорте добавится даже приватный атрибут, потому что он явно указан!
Последствия импорта from other_module import * тоже очевидны и я их не рассматриваю.
Наконец-то, пакеты!
Пакет в Python — директория с обязательным модулем __init__.py . Остальное содержимое опционально и может включать в себя и модули, и другие пакеты.
Импортирование пакетов
Пакет с единственным модулем __init__.py при импорте ведёт себя как обычный модуль. Содержимое инициализирующего модуля определяет атрибуты объекта пакета.
Прочие модули пакета и вложенные пакеты не импортируются автоматически вместе с пакетом-родителем, но могут быть импортированы отдельно с указанием полного имени. Важный момент: при импортировании вложенного модуля всегда сначала импортируются модули инициализации всех родительских пакетов (если оные ещё ни разу не импортировались, но об этом я расскажу ниже).
Рассмотрим, к примеру, следующую структуру директорий и файлов:
Когда мы импортируем модуль submodule.py , то фактически происходит следующее (именно в таком порядке):
- загружается и выполняется модуль package/__init__.py ,
- загружается и выполняется package/subpackage/__init__.py ,
- наконец, импортируется package/subpackage/submodule.py .
При импорте package.module предварительно загружается только package/__init__.py .
Так что же, если мы загрузим парочку вложенных модулей, то для каждого будет выполняться загрузка всех __init__.py по дороге? Не будет! Подсистема интерпретатора, отвечающая за загрузку модулей, кэширует уже загруженные пакеты и модули. Каждый конкретный модуль загружается ровно один раз, в том числе и инициализирующие модули __init__.py (короткие имена модулей хоть и одинаковы, но полные имена всегда разные). Все последующие импортирования модуля не приводят к его загрузке, только лишь нужные атрибуты копируются в соответствующие области видимости.
Пакеты и __all__
В целом атрибут __all__ в модуле инициализации пакета ведёт себя так же, как и в случае с обычным модулем. Но если при импорте пакета «со звёздочкой» среди перечисленных имён встретится имя вложенного модуля, а сам модуль не окажется импортирован ранее в этом же __init__.py , то этот модуль импортируется неявно! Очередной пример это продемонстрирует.
Вот структура пакета:
Файл же package/__init__.py содержит следующее (и только это!):
__all__ = ('a', 'b')
А импортируем мы from package import * . В области видимости у нас окажутся объекты модулей a и b под своими именами (без полного пути, то есть без package. ). При этом сами модули в коде нигде явно не импортируются! Такая вот «автомагия».
Указанный автоматизм достаточно ограничен: не работает «вглубь», например — не импортирует «через звёздочку» указанные модули и подпакеты. Если же вам вдруг такого захочется, вы всегда сможете на соответствующих уровнях в __init__.py сделать from x import * и получить в корневом пакете плоскую область видимости со всем нужным содержимым. Но такое нужно довольно редко, потому что «не помогает» ни IDE, ни ручному поиску по коду. Впрочем, знать о фиче и иметь её в виду — не вредно, как мне кажется.
Изучайте Python на Хекслете Первые курсы в профессии Python-программист доступны бесплатно сразу после регистрации. Начните сегодня, учитесь в комфортном для вас темпе.
Пакеты, модули и точки входа
С модулем __init__.py разобрались. Настала очередь модуля __main__.py . Этот модуль позволяет сделать пакет исполняемым посредством вызова python -m … . Те из вас, кому знакомо оформление точки входа в модулях, могут догадаться, откуда ноги растут у магического выражения __name__ == ‘__main__’ — да, отсюда! Для остальных напоминаю: чтобы модуль сделать «исполняемым, но не при импорте», в конец модуля дописывается конструкция
if __name__ == '__main__': main() # тут что-то выполняем
У модуля, который скармливается интерпретатору напрямую ( python file.py ) или в роли претендента на запуск ( python -m module ), атрибут __name__ будет содержать то самое магическое ‘__main__’ . А в остальное время атрибут содержит полное имя модуля. С помощью условия, показанного выше, модуль может решить, что делать при запуске.
У пакетов роль атрибута выполняет специальный файл __main__.py . Когда мы запустим пакет через python path/to/package или python -m package , интерпретатор будет искать и выполнять именно этот файл.
Более того, модули __main__ нельзя импортировать обычным способом, поэтому можно не бояться случайного импорта и писать команды прямо на верхнем уровне: всё равно странно в модуле с именем __main__ проверять, что его имя равно __main__ (хе-хе!).
А ещё модуль __main__.py удобен тем, что его можно класть в корень вашего проекта, после чего запускать проект можно будет с помощью команды python . ! Лаконично, не правда ли?
PEP 420, или неявные пространства имён
Раз уж развёл ликбез, расскажу и про эту штуку.
Долгое время в Python пакеты были обязаны иметь файл __init__.py — наличие этого файла позволяло отличить пакет от обычной директории с модулями (с которыми Python работать не мог). Но с версии Python3.3 вступил в силу PEP 420, позволяющий создавать пространства имён «на вырост».
Теперь вы можете создавать пакет без __init__.py , и такой пакет сможет существовать полноценно, разве что при импорте содержимого не будет производиться инициализация. Но, конечно же, данное изменение делалось не с целью сэкономить на файлах. Подобные пакеты могут встречаться в путях поиска пакетов (о поиске пакетов я ниже расскажу) более одного раза: все встреченные структуры с общим корневым именем при загрузке схлопнутся в одно пространство имён.
Тут стоит отметить, что с полноценными пакетами подобное не срабатывало ранее и не будет работать в будущем. Если среди путей пакет с модулем инициализации находится в первый раз, все последующие пакеты с тем же именем будут проигнорированы. Это защищает вас от смешивания сторонних пакетов с системными. И даже просто от ошибок именования: назвав пакет так же, как называется встроенный пакет или модуль, вы получите ошибку — ваши определения не будут импортироваться.
Пакеты — пространства имён (Namespace Packages, NP) — а именно так называются пакеты без инициализации — не могут объединяться с полноценными пакетами, поэтому добавить что-то в системный пакет вам также не удастся. И тут всё защищено!
Какая же польза от неявных пространств имён? А вы представьте себя авторами, скажем, игрового движка. Вы хотите весь код держать в общем пространстве имён engine , но при этом не желаете, чтобы весь код поставлялся одним дистрибутивом (не каждому же пользователю нужны все-все компоненты движка). С NP вы можете в нескольких дистрибутивах использовать общее корневое имя engine , но разные подпакеты и подмодули. А на выходе вы получите возможность делать импорты вида
from engine import graphics, sound
Важно: помните, если встретятся обычный пакет и NP с одинаковым именем, то победит обычный пакет! А NP, сколько бы их не было, не будут загружены!
Циклические импорты
Если вдруг вы захотите в один модуль импортировать другой, а другой захочет, пусть даже и не напрямую, импортировать первый, то вы получите ImportError . Потому что у вас случится циклический импорт. Про оный нужно просто знать и стараться архитектурить код так, чтобы циклов не случалось.
Если же приспичивает, и импортировать что-то «ну очень нужно», то можно попробовать обойтись локальным импортом:
### foo.py import bar A = bar(42)
### bar.py # import foo — тут не импортируем def bar(x): return x + 1 def baz(): import foo # а тут уже можно return foo.A
Да, это костыль. Но иногда полезный. В идеале — до ближайшего большого рефакторинга. Поэтому настраивайте linter на ловлю локальных импортов и стремитесь убирать такие костыли хоть когда-нибудь!
Поиск пакетов и модулей
Пайтон ищет модули и пакеты в директориях, во время исполнения перечисленных в списке sys.path — по порядку от первого пути к последнему.
В этом списке пути до стандартных библиотек обычно расположены раньше, чем директории со сторонними пакетами, чтобы нельзя было случайно заменить стандартный пакет сторонним (помним: кто первый, того и тапки — среди нескольких с одинаковыми именами загружается первый попавшийся пакет).
В списке путей (обычно в начале) присутствует и путь » , означающий текущую директорию. Это, в свою очередь, означает, что модули и пакет в текущем проекте имеют больший приоритет.
Обычно пути трогать не нужно, всё вполне нормально «работает само». Но если очень хочется, то путей у вас несколько:
- Использовать переменную окружения PYTHONPATH (значение — строка с путями, разделёнными символом : ),
- Во время исполнения изменить sys.path .
Первый способ — простой и понятный. Не сложнее добавления пути до исполняемых файлов в PATH (даже синтаксис тот же).
Второй способ — сложный и требующий внимательности. Дело в том, что sys.path нужно изменять максимально рано — где-нибудь в точке входа. Если не торопиться менять sys.path , то что-то уже может успеть загрузиться до того, как вы перестроите пути для поиска пакетов. А ведь эта загрузка может произойти в другом потоке исполнения! Отлаживать проблемы с очерёдностью загрузки модулей сложно. Лучше просто их не создавать.
Кстати, когда вы используете виртуальные окружения, sys.path будет содержать пути до локальных копий стандартных библиотек. Именно это позволяет виртуальному окружению быть самодостаточным (работать на любой машине с подходящей ОС — даже без установленного в систему Python!).
Что не было раскрыто?
Я специально не стал рассказывать про
- создание модулей и пакетов на лету (без использования файлов исходников);
- загрузку модулей не с диска, а из других источников;
- расширение подсистемы импортирования с целью загрузки в виде объектов-модулей чего-то, не являющегося кодом вовсе (XML, CSV, JSON).
Темы эти насколько интересны, настолько и велики. На наше счастье, самим разбираться в такой тонкой и сложной машинерии приходится редко. Мы просто пользуемся готовыми магическими артефактами, а зачаровывают их другие 🙂 Если же вы захотите научиться магии, документация вам в руки.
Учимся создавать пакеты Python

Почему важно уметь создавать пакеты Python?
• Пакеты легко устанавливаются (pip install demo).
• Пакеты упрощают разработку (Команда pip install -e устанавливает ваш пакет и следит за тем, чтобы он сам обновлялся в ходе всего процесса разработки).
• Пакеты легко запускать и тестировать (from demo.main import say_hello, а затем тестируем функцию).
• Пакеты легко версионировать, при этом вы не рискуете нарушить работу кода, зависящего от этого пакета (pip install demo==1.0.3).

В чем отличия между библиотекой, пакетом и модулем:
• Модуль: это .py-файл, в котором содержатся функции, образующие некоторое единство
• Пакет: это коллекция модулей, которую можно распространять
• Библиотека: это пакет, не учитывающий контекста
Заключать код Python в пакеты достаточно просто. Для этого вам понадобится всего один скрипт setup.py, позволяющий упаковать код сразу в нескольких форматах для распространения.
1. Подготовка к упаковке
Давайте воспользуемся такой структурой каталогов, которая описана в этом посте, и создадим здесь виртуальное окружение:
➜ tree -a -L 2 . ├── .venv │ └── . ├── Pipfile ├── Pipfile.lock ├── src │ └── demo │ └── main.py └── tests └── demo └── . 9 directories, 3 files
Создаем файл setup.py в корневом каталоге. В этом файле мы будем описывать, каким именно образом хотим упаковать наш код. Для начала напишем следующее:
"""Скрипт Setup.py для проекта по упаковке.""" from setuptools import setup, find_packages import json import os def read_pipenv_dependencies(fname): """Получаем из Pipfile.lock зависимости по умолчанию.""" filepath = os.path.join(os.path.dirname(__file__), fname) with open(filepath) as lockfile: lockjson = json.load(lockfile) return [dependency for dependency in lockjson.get('default')] if __name__ == '__main__': setup( name='demo', version=os.getenv('PACKAGE_VERSION', '0.0.dev0'), package_dir=, packages=find_packages('src', include=[ 'demo*' ]), description='A demo package.', install_requires=[ *read_pipenv_dependencies('Pipfile.lock'), ] )
Теперь можно вызвать этот скрипт, который позволяет упаковать ваш код несколькими способами:
python setup.py develop # ничего не генерировать, просто установить локально python setup.py bdist_egg # сгенерировать дистрибутив «яйцо», не включать зависимости python setup.py bdist_wheel # сгенерировать версионированное «колесо», включить зависимости python setup.py sdist --formats=zip,gztar,bztar,ztar,tar # исходный код
Давайте запустим первый вариант из списка. Если все пройдет успешно, то вы сможете импортировать ваш код следующим образом:
from demo.main import say_hello
Примечание:
Если выдается сообщение “No module named demo…», то нужно добавить пустой файл __init__.py во все каталоги, из которых вы хотите импортировать. В нашем примере сюда включается только каталог demo. Подробнее об этих файлах __init__.py можно почитать здесь.
Теперь, когда мы в состоянии установить проект, давайте внимательнее рассмотрим аргументы, передаваемые функции setuptools.setup:
1. name: имя вашей функции
2. version: результатом каждого изменения, вносимого в код, должна быть новая версия пакета; в противном случае возможна ситуация, в которой разработчики устанавливают прежнюю версию пакета, которая вдруг станет функционировать не так как раньше и сломает код.
3. packages: список путей ко всем вашим файлам python
4. install_requires: список имен и версий пакетов (точно как в файле requirements.txt)
Как видите, я написал простую функцию read_pipenv_dependencies для считывания из Pipfile.lock зависимостей, не попадающих в разработку (non-dev). В данном случае я не хочу задавать зависимости вручную. Также я воспользуюсь os.getenv для считывания переменной окружения и определения версии пакета – пожалуй, это хорошие сюжеты для новых постов.
2. Документация
Точно как при считывании Pipfile.lock для указания зависимостей, я могу прочитать и файл README.md, чтобы отобразить полезную документацию как long_description. Подробнее о том, как это делается, рассказано в packaging.python.org.
Кроме того, можно создать полноценную веб-страницу с документацией при помощи readthedocs и sphinx. Создаем каталог для вашей документации:
mkdir docs
pipenv install -d sphinx
Командой quickstart генерируем каталог с исходниками для вашей документации:
sphinx-quickstart
Теперь можно приступать к наполнению файла docs/index.rst самой документацией. Подробнее о том, как автоматизировать этот процесс, рассказано на сайте sphinx-doc.org.
3. Линтинг и тестирование
В рамках процесса упаковки целесообразно применить статический анализ кода, линтинг и тестирование.
pipenv install -d mypy autopep8 \ flake8 pytest bandit pydocstyle
В данном случае предпочтительно выполнить команду, которая выполнила бы стиль кода, прогнала несколько тестов и проверок, прежде, чем код можно будет зафиксировать и скинуть в удаленный репозиторий. Это делается для того, чтобы спровоцировать отказ конвейера сборки, если тесты не пройдут.
4. Makefile
По мере того, как мы быстро вводим все новые команды, нужные для упаковки нашего конкретного проекта, распространенные команды полезно записывать. В большинстве инструментов для автоматизации сборки (например, в Gradle или npm) эта возможность предоставляется по умолчанию.
Make – это инструмент, организующий компиляцию кода. Традиционно используется в c-ориентированных проектах. Но с его помощью можно выполнять и любые другие команды.
По умолчанию при использовании make выполняется первая команда из списка. Таким образом, в следующем примере будет выполнена make help, а на экран будет выведено содержимое Makefile.
Если сделать make test, то сначала будет выполнена make dev, поскольку в файле Makefile она указана как зависимость:
help: @echo "Tasks in \033[1;32mdemo\033[0m:" @cat Makefile lint: mypy src --ignore-missing-imports flake8 src --ignore=$(shell cat .flakeignore) dev: pip install -e . test: dev pytest --doctest-modules --junitxml=junit/test-results.xml bandit -r src -f xml -o junit/security.xml || true build: clean pip install wheel python setup.py bdist_wheel clean: @rm -rf .pytest_cache/ .mypy_cache/ junit/ build/ dist/ @find . -not -path './.venv*' -path '*/__pycache__*' -delete @find . -not -path './.venv*' -path '*/*.egg-info*' -delete
Теперь, как видите, новым разработчикам достаточно легко внести свой вклад в проект. Распространенные команды у них как на ладони и, например, сразу видно, как собрать колесо: make build.
5. Установка колеса
Если запустить make build, программа использует файл setup.py, чтобы создать дистрибутив колеса. Файл .whl находится в каталоге dist/, в имени файла должно присутствовать 0.0.dev0. Теперь можно указать переменную окружения, чтобы изменить версию колеса:
export PACKAGE_VERSION='1.0.0' make build ls dist
Имея колесо, можно создать где-нибудь на ПК новый каталог, скопировать в него колесо, а затем установить его при помощи:
mkdir test-whl && cd test-whl pipenv shell pip install *.whl
Вывод списка установленных файлов:
pip list
6. Включить конфигурационные файлы
Добавить данные в пакет можно и другим способом, включив в скрипт setup.py следующие строки:
Примечание:
Dозможно, не будет работать на распределенных системах (например, в Databricks).
if __name__ == '__main__': setup( data_files=[ ('data', ['data/my-config.json']) ] )
После этого можно будет прочитать файл при помощи следующей функции:
def get_cfg_file(filename: str, foldername: str) -> dict: """получить конфигурационный файл при помощи свойства 'data_files' из скрипта setup.py. """ if not isinstance(foldername, str): raise ValueError('Foldername must be string.') if foldername[0] == '/': raise ValueError('Foldername must not start with \'/\'') if not isinstance(filename, str): raise ValueError('Filename must be string.') # Сначала попытается считать файл из того места, в котором он установлен # Это касается только установок .whl # В противном случае файл будет считываться напрямую try: filepath = os.path.join(sys.prefix, foldername, filename) with open(filepath) as f: return json.load(f) except FileNotFoundError: filepath = os.path.join(foldername, filename) with open(filepath) as f: return json.load(f)
Если снова создать колесо и установить его в виртуальной среде в новом каталоге, не копируя файл данных, то можно будет обратиться к данным, выполнив вышеприведенную функцию.
7. DevOps
В рамках процесса упаковки мы хотим интегрировать изменения, внесенные многими участниками и автоматизировать интеграцию, так как для успешного релиза новой версии требуется выполнять множество повторяющихся процессов.
Здесь рассмотрим для примера Azure DevOps, где на git tags, а также в ветке master будет инициироваться процесс, представленный ниже.
Посмотрите код, и ниже мы обсудим его различные стадии и задачи:
resources: - repo: self trigger: - master - refs/tags/v* variables: python.version: "3.7" project: demo feed: demo major_minor: $[format('.', pipeline.startTime)] counter_unique_key: $[format('.demo', variables.major_minor)] patch: $[counter(variables.counter_unique_key, 0)] fallback_tag: $(major_minor).dev$(patch) stages: - stage: Test jobs: - job: Test displayName: Test steps: - task: UsePythonVersion@0 displayName: "Use Python $(python.version)" inputs: versionSpec: "$(python.version)" - script: pip install pipenv && pipenv install -d --system --deploy --ignore-pipfile displayName: "Install dependencies" - script: pip install typed_ast && make lint displayName: Lint - script: pip install pathlib2 && make test displayName: Test - task: PublishTestResults@2 displayName: "Publish Test Results junit/*" condition: always() inputs: testResultsFiles: "junit/*" testRunTitle: "Python $(python.version)" - stage: Build dependsOn: Test jobs: - job: Build displayName: Build steps: - task: UsePythonVersion@0 displayName: "Use Python $(python.version)" inputs: versionSpec: "$(python.version)" - script: "pip install wheel twine" displayName: "Wheel and Twine" - script: | # Получить версию по тегу git (v1.0.0) -> (1.0.0) git_tag=`git describe --abbrev=0 --tags | cut -d'v' -f 2` echo "##vso[task.setvariable variable=git_tag]$git_tag" displayName: Set GIT_TAG variable if tag is pushed condition: contains(variables['Build.SourceBranch'], 'refs/tags/v') - script: | # Получить переменные, совместно используемые разными заданиями GIT_TAG=$(git_tag) FALLBACK_TAG=$(fallback_tag) echo GIT TAG: $GIT_TAG, FALLBACK_TAG: $FALLBACK_TAG # Экспортировать переменную, так, чтобы python мог ее принять export PACKAGE_VERSION=$> echo Version used in setup.py: $PACKAGE_VERSION # Использовать PACKAGE_VERSION в setup() python setup.py bdist_wheel displayName: Build - task: CopyFiles@2 displayName: Copy dist files inputs: sourceFolder: dist/ contents: demo*.whl targetFolder: $(Build.ArtifactStagingDirectory) flattenFolders: true - task: PublishBuildArtifacts@1 displayName: PublishArtifact inputs: pathtoPublish: $(Build.ArtifactStagingDirectory) ArtifactName: demo.whl - task: TwineAuthenticate@1 inputs: artifactFeed: $(project)/$(feed) - script: | twine upload -r $(feed) --config-file $(PYPIRC_PATH) dist/* displayName: PublishFeed
На этапе Test мы устанавливаем проект в контейнер конвейера, не создавая виртуального окружения. Затем выполняем команды make lint и make test, точно как вы сделали бы это на вашей машине.
На этапе Build попытаемся извлечь версию пакета, ориентируясь на тег git, а еще соберем резервную версию пакета. Выполним команду python setup.py bdist_wheel для сборки колеса, учитывая, что у нас уже установлена переменная окружения, соответствующая версии пакета. Наконец, мы публикуем артефакт в числе других артефактов Azure DevOps и (по желанию) можем выложить в ленту.
Чтобы опубликовать пакет в ленте, вам потребуется файл .pypirc, а затем вы можете скопировать содержимое ленты, созданной в Azure DevOps. Выглядеть файл будет примерно так:
[distutils] Index-servers = stefanschenk [stefanschenk] Repository = https://pkgs.dev.azure.com/stefanschenk/_packaging/stefanschenk/pypi/upload
О том, как устанавливать пакеты из частной ленты, рассказано здесь.
- python
- программирование
- web-разработка
PEP 8 — руководство по написанию кода на Python

Этот документ описывает соглашение о том, как писать код для языка python, включая стандартную библиотеку, входящую в состав python.
PEP 8 создан на основе рекомендаций Гуидо ван Россума с добавлениями от Барри. Если где-то возникал конфликт, мы выбирали стиль Гуидо. И, конечно, этот PEP может быть неполным (фактически, он, наверное, никогда не будет закончен).
Ключевая идея Гуидо такова: код читается намного больше раз, чем пишется. Собственно, рекомендации о стиле написания кода направлены на то, чтобы улучшить читаемость кода и сделать его согласованным между большим числом проектов. В идеале, весь код будет написан в едином стиле, и любой сможет легко его прочесть.
Это руководство о согласованности и единстве. Согласованность с этим руководством очень важна. Согласованность внутри одного проекта еще важнее. А согласованность внутри модуля или функции — самое важное. Но важно помнить, что иногда это руководство неприменимо, и понимать, когда можно отойти от рекомендаций. Когда вы сомневаетесь, просто посмотрите на другие примеры и решите, какой выглядит лучше.
Две причины для того, чтобы нарушить данные правила:
- Когда применение правила сделает код менее читаемым даже для того, кто привык читать код, который следует правилам.
- Чтобы писать в едином стиле с кодом, который уже есть в проекте и который нарушает правила (возможно, в силу исторических причин) — впрочем, это возможность переписать чужой код.
Внешний вид кода
Отступы
Используйте 4 пробела на каждый уровень отступа.
Продолжительные строки должны выравнивать обернутые элементы либо вертикально, используя неявную линию в скобках (круглых, квадратных или фигурных), либо с использованием висячего отступа. При использовании висячего отступа следует применять следующие соображения: на первой линии не должно быть аргументов, а остальные строки должны четко восприниматься как продолжение линии.
Неправильно:
Опционально:
Закрывающие круглые/квадратные/фигурные скобки в многострочных конструкциях могут находиться под первым непробельным символом последней строки списка, например:
либо быть под первым символом строки, начинающей многострочную конструкцию:
Пробелы — самый предпочтительный метод отступов.
Табуляция должна использоваться только для поддержки кода, написанного с отступами с помощью табуляции.
Python 3 запрещает смешивание табуляции и пробелов в отступах.
Python 2 пытается преобразовать табуляцию в пробелы.
Когда вы вызываете интерпретатор Python 2 в командной строке с параметром -t, он выдает предупреждения (warnings) при использовании смешанного стиля в отступах, а запустив интерпретатор с параметром -tt, вы получите в этих местах ошибки (errors). Эти параметры очень рекомендуются!
Максимальная длина строки
Ограничьте длину строки максимум 79 символами.
Для более длинных блоков текста с меньшими структурными ограничениями (строки документации или комментарии), длину строки следует ограничить 72 символами.
Ограничение необходимой ширины окна редактора позволяет иметь несколько открытых файлов бок о бок, и хорошо работает при использовании инструментов анализа кода, которые предоставляют две версии в соседних столбцах.
Некоторые команды предпочитают большую длину строки. Для кода, поддерживающегося исключительно или преимущественно этой группой, в которой могут прийти к согласию по этому вопросу, нормально увеличение длины строки с 80 до 100 символов (фактически увеличивая максимальную длину до 99 символов), при условии, что комментарии и строки документации все еще будут 72 символа.
Стандартная библиотека Python консервативна и требует ограничения длины строки в 79 символов (а строк документации/комментариев в 72).
Предпочтительный способ переноса длинных строк является использование подразумеваемых продолжений строк Python внутри круглых, квадратных и фигурных скобок. Длинные строки могут быть разбиты на несколько строк, обернутые в скобки. Это предпочтительнее использования обратной косой черты для продолжения строки.
Обратная косая черта все еще может быть использована время от времени. Например, длинная конструкция with не может использовать неявные продолжения, так что обратная косая черта является приемлемой:
Ещё один случай - assert.
Сделайте правильные отступы для перенесённой строки. Предпочтительнее вставить перенос строки после логического оператора, но не перед ним. Например:
Отделяйте функции верхнего уровня и определения классов двумя пустыми строками.
Определения методов внутри класса разделяются одной пустой строкой.
Дополнительные пустые строки возможно использовать для разделения различных групп похожих функций. Пустые строки могут быть опущены между несколькими связанными однострочниками (например, набор фиктивных реализаций).
Используйте пустые строки в функциях, чтобы указать логические разделы.
Python расценивает символ control+L как незначащий (whitespace), и вы можете использовать его, потому что многие редакторы обрабатывают его как разрыв страницы — таким образом логические части в файле будут на разных страницах. Однако, не все редакторы распознают control+L и могут на его месте отображать другой символ.
Кодировка исходного файла
Кодировка Python должна быть UTF-8 (ASCII в Python 2).
Файлы в ASCII (Python 2) или UTF-8 (Python 3) не должны иметь объявления кодировки.
В стандартной библиотеке, нестандартные кодировки должны использоваться только для целей тестирования, либо когда комментарий или строка документации требует упомянуть имя автора, содержащего не ASCII символы; в остальных случаях использование \x, \u, \U или \N — наиболее предпочтительный способ включить не ASCII символы в строковых литералах.
Начиная с версии python 3.0 в стандартной библиотеке действует следующее соглашение: все идентификаторы обязаны содержать только ASCII символы, и означать английские слова везде, где это возможно (во многих случаях используются сокращения или неанглийские технические термины). Кроме того, строки и комментарии тоже должны содержать лишь ASCII символы. Исключения составляют: (а) test case, тестирующий не-ASCII особенности программы, и (б) имена авторов. Авторы, чьи имена основаны не на латинском алфавите, должны транслитерировать свои имена в латиницу.
Проектам с открытым кодом для широкой аудитории также рекомендуется использовать это соглашение.
Импорты
- Каждый импорт, как правило, должен быть на отдельной строке. Правильно:
Вставляйте пустую строку между каждой группой импортов.
Указывайте спецификации __all__ после импортов.
Пробелы в выражениях и инструкциях
Избегайте использования пробелов в следующих ситуациях:
- Непосредственно внутри круглых, квадратных или фигурных скобок. Правильно:
- Всегда окружайте эти бинарные операторы одним пробелом с каждой стороны: присваивания (=, +=, -= и другие), сравнения (==, , !=, <>, =, in, not in, is, is not), логические (and, or, not).
- Если используются операторы с разными приоритетами, попробуйте добавить пробелы вокруг операторов с самым низким приоритетом. Используйте свои собственные суждения, однако, никогда не используйте более одного пробела, и всегда используйте одинаковое количество пробелов по обе стороны бинарного оператора. Правильно:
Комментарии, противоречащие коду, хуже, чем отсутствие комментариев. Всегда исправляйте комментарии, если меняете код!
Комментарии должны являться законченными предложениями. Если комментарий — фраза или предложение, первое слово должно быть написано с большой буквы, если только это не имя переменной, которая начинается с маленькой буквы (никогда не изменяйте регистр переменной!).
Если комментарий короткий, можно опустить точку в конце предложения. Блок комментариев обычно состоит из одного или более абзацев, составленных из полноценных предложений, поэтому каждое предложение должно оканчиваться точкой.
Ставьте два пробела после точки в конце предложения.
Программисты, которые не говорят на английском языке, пожалуйста, пишите комментарии на английском, если только вы не уверены на 120%, что ваш код никогда не будут читать люди, не знающие вашего родного языка.
Блоки комментариев
Блок комментариев обычно объясняет код (весь, или только некоторую часть), идущий после блока, и должен иметь тот же отступ, что и сам код. Каждая строчка такого блока должна начинаться с символа # и одного пробела после него (если только сам текст комментария не имеет отступа).
Абзацы внутри блока комментариев разделяются строкой, состоящей из одного символа #.
«Встрочные» комментарии
Старайтесь реже использовать подобные комментарии.
Такой комментарий находится в той же строке, что и инструкция. «Встрочные» комментарии должны отделяться по крайней мере двумя пробелами от инструкции. Они должны начинаться с символа # и одного пробела.
Комментарии в строке с кодом не нужны и только отвлекают от чтения, если они объясняют очевидное. Не пишите вот так:
Впрочем, такие комментарии иногда полезны:
Строки документации
- Пишите документацию для всех публичных модулей, функций, классов, методов. Строки документации необязательны для приватных методов, но лучше написать, что делает метод. Комментарий нужно писать после строки с def.
- PEP 257 объясняет, как правильно и хорошо документировать. Заметьте, очень важно, чтобы закрывающие кавычки стояли на отдельной строке. А еще лучше, если перед ними будет ещё и пустая строка, например:
Контроль версий
Если вам нужно использовать Subversion, CVS или RCS в ваших исходных кодах, делайте вот так:
Вставляйте эти строки после документации модуля перед любым другим кодом и отделяйте их пустыми строками по одной до и после.
Соглашения по именованию
Соглашения по именованию переменных в python немного туманны, поэтому их список никогда не будет полным — тем не менее, ниже мы приводим список рекомендаций, действующих на данный момент. Новые модули и пакеты должны быть написаны согласно этим стандартам, но если в какой-либо уже существующей библиотеке эти правила нарушаются, предпочтительнее писать в едином с ней стиле.
Главный принцип
Имена, которые видны пользователю как часть общественного API должны следовать конвенциям, которые отражают использование, а не реализацию.
Описание: Стили имен
Существует много разных стилей. Поможем вам распознать, какой стиль именования используется, независимо от того, для чего он используется.
Обычно различают следующие стили:
- b (одиночная маленькая буква)
- B (одиночная заглавная буква)
- lowercase (слово в нижнем регистре)
- lower_case_with_underscores (слова из маленьких букв с подчеркиваниями)
- UPPERCASE (заглавные буквы)
- UPPERCASE_WITH_UNDERSCORES (слова из заглавных букв с подчеркиваниями)
- CapitalizedWords (слова с заглавными буквами, или CapWords, или CamelCase). Замечание: когда вы используете аббревиатуры в таком стиле, пишите все буквы аббревиатуры заглавными — HTTPServerError лучше, чем HttpServerError.
- mixedCase (отличается от CapitalizedWords тем, что первое слово начинается с маленькой буквы)
- Capitalized_Words_With_Underscores (слова с заглавными буквами и подчеркиваниями — уродливо!)
Ещё существует стиль, в котором имена, принадлежащие одной логической группе, имеют один короткий префикс. Этот стиль редко используется в python, но мы упоминаем его для полноты. Например, функция os.stat() возвращает кортеж, имена в котором традиционно имеют вид st_mode, st_size, st_mtime и так далее. (Так сделано, чтобы подчеркнуть соответствие этих полей структуре системных вызовов POSIX, что помогает знакомым с ней программистам).
В библиотеке X11 используется префикс Х для всех public-функций. В python этот стиль считается излишним, потому что перед полями и именами методов стоит имя объекта, а перед именами функций стоит имя модуля.
В дополнение к этому, используются следующие специальные формы записи имен с добавлением символа подчеркивания в начало или конец имени:
- _single_leading_underscore: слабый индикатор того, что имя используется для внутренних нужд. Например, from M import * не будет импортировать объекты, чьи имена начинаются с символа подчеркивания.
- single_trailing_underscore_: используется по соглашению для избежания конфликтов с ключевыми словами языка python, например:
Предписания: соглашения по именованию
Имена, которых следует избегать
Никогда не используйте символы l (маленькая латинская буква «эль»), O (заглавная латинская буква «о») или I (заглавная латинская буква «ай») как однобуквенные идентификаторы.
В некоторых шрифтах эти символы неотличимы от цифры один и нуля. Если очень нужно l, пишите вместо неё заглавную L.
Имена модулей и пакетов
Модули должны иметь короткие имена, состоящие из маленьких букв. Можно использовать символы подчеркивания, если это улучшает читабельность. То же самое относится и к именам пакетов, однако в именах пакетов не рекомендуется использовать символ подчёркивания.
Так как имена модулей отображаются в имена файлов, а некоторые файловые системы являются нечувствительными к регистру символов и обрезают длинные имена, очень важно использовать достаточно короткие имена модулей — это не проблема в Unix, но, возможно, код окажется непереносимым в старые версии Windows, Mac, или DOS.
Когда модуль расширения, написанный на С или C++, имеет сопутствующий python-модуль (содержащий интерфейс высокого уровня), С/С++ модуль начинается с символа подчеркивания, например, _socket.
Имена классов
Имена классов должны обычно следовать соглашению CapWords.
Вместо этого могут использоваться соглашения для именования функций, если интерфейс документирован и используется в основном как функции.
Обратите внимание, что существуют отдельные соглашения о встроенных именах: большинство встроенных имен — одно слово (либо два слитно написанных слова), а соглашение CapWords используется только для именования исключений и встроенных констант.
Имена исключений
Так как исключения являются классами, к исключениям применяется стиль именования классов. Однако вы можете добавить Error в конце имени (если, конечно, исключение действительно является ошибкой).
Имена глобальных переменных
Будем надеяться, что глобальные переменные используются только внутри одного модуля. Руководствуйтесь теми же соглашениями, что и для имен функций.
Добавляйте в модули, которые написаны так, чтобы их использовали с помощью from M import *, механизм __all__, чтобы предотвратить экспортирование глобальных переменных. Или же, используйте старое соглашение, добавляя перед именами таких глобальных переменных один символ подчеркивания (которым вы можете обозначить те глобальные переменные, которые используются только внутри модуля).
Имена функций
Имена функций должны состоять из маленьких букв, а слова разделяться символами подчеркивания — это необходимо, чтобы увеличить читабельность.
Стиль mixedCase допускается в тех местах, где уже преобладает такой стиль, для сохранения обратной совместимости.
Аргументы функций и методов
Всегда используйте self в качестве первого аргумента метода экземпляра объекта.
Всегда используйте cls в качестве первого аргумента метода класса.
Если имя аргумента конфликтует с зарезервированным ключевым словом python, обычно лучше добавить в конец имени символ подчеркивания, чем исказить написание слова или использовать аббревиатуру. Таким образом, class_ лучше, чем clss. (Возможно, хорошим вариантом будет подобрать синоним).
Имена методов и переменных экземпляров классов
Используйте тот же стиль, что и для имен функций: имена должны состоять из маленьких букв, а слова разделяться символами подчеркивания.
Используйте один символ подчёркивания перед именем для непубличных методов и атрибутов.
Чтобы избежать конфликтов имен с подклассами, используйте два ведущих подчеркивания.
Python искажает эти имена: если класс Foo имеет атрибут с именем __a, он не может быть доступен как Foo.__a. (Настойчивый пользователь все еще может получить доступ, вызвав Foo._Foo__a.) Вообще, два ведущих подчеркивания должны использоваться только для того, чтобы избежать конфликтов имен с атрибутами классов, предназначенных для наследования.
Примечание: есть некоторые разногласия по поводу использования __ имена (см. ниже).
Константы
Константы обычно объявляются на уровне модуля и записываются только заглавными буквами, а слова разделяются символами подчеркивания. Например: MAX_OVERFLOW, TOTAL.
Проектирование наследования
Обязательно решите, каким должен быть метод класса или экземпляра класса (далее — атрибут) — публичный или непубличный. Если вы сомневаетесь, выберите непубличный атрибут. Потом будет проще сделать его публичным, чем наоборот.
Публичные атрибуты — это те, которые будут использовать другие программисты, и вы должны быть уверены в отсутствии обратной несовместимости. Непубличные атрибуты, в свою очередь, не предназначены для использования третьими лицами, поэтому вы можете не гарантировать, что не измените или не удалите их.
Мы не используем термин «приватный атрибут», потому что на самом деле в python таких не бывает.
Другой тип атрибутов классов принадлежит так называемому API подклассов (в других языках они часто называются protected). Некоторые классы проектируются так, чтобы от них наследовали другие классы, которые расширяют или модифицируют поведение базового класса. Когда вы проектируете такой класс, решите и явно укажите, какие атрибуты являются публичными, какие принадлежат API подклассов, а какие используются только базовым классом.
Теперь сформулируем рекомендации:
- Открытые атрибуты не должны иметь в начале имени символа подчеркивания.
- Если имя открытого атрибута конфликтует с ключевым словом языка, добавьте в конец имени один символ подчеркивания. Это более предпочтительно, чем аббревиатура или искажение написания (однако, у этого правила есть исключение — аргумента, который означает класс, и особенно первый аргумент метода класса (class method) должен иметь имя cls).
- Назовите простые публичные атрибуты понятными именами и не пишите сложные методы доступа и изменения (accessor/mutator, get/set, — прим. перев.) Помните, что в python очень легко добавить их потом, если потребуется. В этом случае используйте свойства (properties), чтобы скрыть функциональную реализацию за синтаксисом доступа к атрибутам. Примечание 1: Свойства (properties) работают только в классах нового стиля (в Python 3 все классы являются таковыми). Примечание 2: Постарайтесь избавиться от побочных эффектов, связанным с функциональным поведением; впрочем, такие вещи, как кэширование, вполне допустимы. Примечание 3: Избегайте использования вычислительно затратных операций, потому что из-за записи с помощью атрибутов создается впечатление, что доступ происходит (относительно) быстро.
- Если вы планируете класс таким образом, чтобы от него наследовались другие классы, но не хотите, чтобы подклассы унаследовали некоторые атрибуты, добавьте в имена два символа подчеркивания в начало, и ни одного — в конец. Механизм изменения имен в python сработает так, что имя класса добавится к имени такого атрибута, что позволит избежать конфликта имен с атрибутами подклассов. Примечание 1: Будьте внимательны: если подкласс будет иметь то же имя класса и имя атрибута, то вновь возникнет конфликт имен. Примечание 2: Механизм изменения имен может затруднить отладку или работу с __getattr__(), однако он хорошо документирован и легко реализуется вручную. Примечание 3: Не всем нравится этот механизм, поэтому старайтесь достичь компромисса между необходимостью избежать конфликта имен и возможностью доступа к этим атрибутам.
Общие рекомендации
- Код должен быть написан так, чтобы не зависеть от разных реализаций языка (PyPy, Jython, IronPython, Pyrex, Psyco и пр.). Например, не полагайтесь на эффективную реализацию в CPython конкатенации строк в выражениях типа a+=b или a=a+b. Такие инструкции выполняются значительно медленнее в Jython. В критичных к времени выполнения частях программы используйте ».join() — таким образом склеивание строк будет выполнено за линейное время независимо от реализации python.
- Сравнения с None должны обязательно выполняться с использованием операторов is или is not, а не с помощью операторов сравнения. Кроме того, не пишите if x, если имеете в виду if x is not None — если, к примеру, при тестировании такая переменная может принять значение другого типа, отличного от None, но при приведении типов может получиться False!
- При реализации методов сравнения, лучше всего реализовать все 6 операций сравнения (__eq__, __ne__, __lt__, __le__, __gt__, __ge__), чем полагаться на то, что другие программисты будут использовать только конкретный вид сравнения. Для минимизации усилий можно воспользоваться декоратором functools.total_ordering() для реализации недостающих методов. PEP 207 указывает, что интерпретатор может поменять y > х на х < y, y >= х на х . Однако, лучше всего осуществить все шесть операций, чтобы не возникало путаницы в других местах.
- Всегда используйте выражение def, а не присваивание лямбда-выражения к имени. Правильно:
Документирование кода в Python
Документирование кода — неотъемлемая часть разработки на Python. Порой документации в коде может быть больше, чем самого кода. Она помогает понять, что делает функция или класс, какие аргументы принимает и что возвращает.
Когда документация и код находятся в разных местах, сопровождать их становиться довольно тяжело. Поэтому на практике документация находится непосредственно рядом с кодом.
Docstring
Docstring — это строковый литерал, который расположен сразу за объявлением модуля, функции, класса или метода. О том, какие существуют соглашения в документировании Python кода описано в документации PEP257 .
Документация для классов
Документация класса создается для самого класса, а также для его методов.
class Speaker: «»»Это docstring класса Speaker»»» def say_something(self): «»»Это docstring метода»»» print(«something»)
После строки документации нужно оставлять пустую строку
Документация для класса может содержать следующую информацию:
- краткое описание класса (+ его поведение);
- описание атрибутов класса;
- описание публичных методов;
- все, что связано с интерфейсом для подклассов.
Для методов класса документация может содержать:
- краткое описание метода (+ его поведение);
- описание аргументов метода;
- побочные эффекты (если таковые возникают при выполнении метода);
- исключения.
Ниже — пример с более подробной документацией класса:
class TextSplitter: «»»Класс TextSplitter используется для разбивки текста на слова Основное применение — парсинг логов на отдельные элементы по указанному разделителю. Note: Возможны проблемы с кодировкой в Windows Attributes ———- file_path : str полный путь до текстового файла lines : list список строк исходного файла Methods ——- load() Читает файл и сохраняет его в виде списка строк в lines get_splitted(split_symbol=» «) Разделяет строки списка по указанному разделителю и возвращает результат в виде списка «»» def __init__(self, file_path: str): self.file_path = file_path.strip() self.lines = [] def load(self) -> None: «»»Метод для загрузки файла в список строк lines Raises —— Exception Если файл пустой вызовется исключение «»» with open(self.file_path, encoding=»utf-8″) as f: for line in f: self.lines.append(line.rstrip(‘\n’)) if len(self.lines) == 0: raise Exception(f»file is empty») def get_splitted(self, split_symbol: str = » «) -> list: «»»Разбивает текстовые строки lines, преобразуя строку в список слов по разделителю Если аргумент split_symbol не задан, в качестве разделителя используется пробел Parameters ———- split_symbol : str, optional разделитель «»» split_list = [] for str_line in self.lines: split_list.append(str_line.split(split_symbol)) return split_list
Документация для пакетов
Документация пакета размещается в файле __init__.py в верхней части файла (начиная с 1-й строки). В ней может быть указано:
- описание пакета;
- список модулей и пакетов, экспортируемых этим модулем;
- автор;
- контактные данные;
- лицензия.
Документация для модулей
Документация модулей аналогична документации классов. Вместо класса и методов в данном случае документируется модуль со всеми его функциями. Размещается в верхней части файла (начиная с 1-й строки).
Форматы Docstring
Строки документации могут иметь различное форматирование. В примере выше мы использовали стиль NumPy. Существуют и другие форматы:
- Google styleguide ->Comments and Docstrings
- Numpydoc docstring guide
- Epydoc
- reStructuredText (reST)
Вывод документации на экран — help() и __doc__
Строки документации доступны:
- из атрибута __doc__ для любого объекта;
- с помощью встроенной функции help().
Выведем документацию с помощью функции help()
>>> import my_module >>> help(my_module) Help on module test: NAME test — Это docstring модуля, он однострочный. FILE /var/www/test.py CLASSES MyClass class MyClass | Это docstring класса. | | Methods defined here: | | my_method(self) | Это docstring метода FUNCTIONS my_function(a) Это многострочный docstring для функции my_function. В многострочном docstring первое предложение кратко описывает работу функции.
Также можно выводить документацию отдельного объекта:
>>> import my_module >>> my_module.__doc__ >>> my_module.my_function.__doc__ >>> my_module.MyClass.__doc__ >>> my_module.MyClass.my_method.__doc__
Pydoc
Для более удобной работы с документацией, в Python существует встроенная библиотека pydoc.
Pydoc автоматически генерирует документацию из Python модулей. Информацию по доступным командам модуля pydoc можно получить набрав в терминале:
Разберем подробнее, что умеет pydoc.
Вывод текста документации
pydoc — покажет текст документации указанного модуля, пакета, функции, класса и т.д. Если содержит «\», Python будет искать документацию по указанному пути.
Для примера, посмотрим документацию встроенного модуля math:
python -m pydoc math Help on built-in module math: NAME math DESCRIPTION This module provides access to the mathematical functions defined by the C standard. FUNCTIONS acos(x, /) Return the arc cosine (measured in radians) of x. acosh(x, /) Return the inverse hyperbolic cosine of x. .
В консоль выведется название модуля, его описание и описание всех функций в модуле.
Поиск по документации
pydoc -k — найдет ключевое слово в документации всех доступных модулей.
Допустим, нам нужно распаковать gzip файл. Поищем слово » gzip «:
python -m pydoc -k gzip _compression — Internal classes used by the gzip, lzma and bz2 modules gzip — Functions that read and write gzipped files. test.test_gzip — Test script for the gzip module.
В списке мы видим модуль gzip . Теперь можно посмотреть его документацию:
python -m pydoc gzip Help on module gzip: NAME gzip — Functions that read and write gzipped files. DESCRIPTION The user of the file doesn’t have to worry about the compression, but random access is not allowed.
По описанию, данный модуль решит нашу задачу.
HTTP сервер с документацией
Для удобства просмотра документации, pydoc позволяет одной командой создать HTTP-сервер:
python -m pydoc -p 331 Server ready at http://localhost:331/ Server commands: [b]rowser, [q]uit server>
Теперь можно перейти в браузер и зайти на http://localhost:331/
Для остановки сервера введите » q » и нажмите » Enter «:
server> q Server stopped
Также HTTP-сервер доступен через python -m pydoc -b – эта команда создаст сервер на свободном порту, откроет браузер и перейдет на нужную страницу.
Запись документации в файл
python -m pydoc -w sqlite3 — запишем файл с документацией по модулю sqlite3 в html файл.
Автодокументирование кода
Для того чтобы облегчить написание документации и улучшить ее в целом, существуют различные Python-пакеты. Один из них — pyment .
Pyment работает следующим образом:
- Анализирует один или несколько скриптов.
- Получает существующие строки документации.
- Генерирует отформатированные строки документации со всеми параметрами, значениями по умолчанию и т.д.
- Далее вы можете применить сгенерированные строки к своим файлам.
Этот инструмент особенно полезен когда код плохо задокументирован, или когда документация вовсе отсутствует. Также pyment будет полезен в команде разработчиков для форматирования документации в едином стиле.
pip install pyment
pyment myfile.py # для файла pyment -w myfile.py # для файла + запись в файл pyment my/folder/ # для всех файлов в папке
Для большинства IDE также существуют плагины, помогающие документировать код:
- AutoDocstring – для VS Code.
- AutoDocstring – для SublimeText.
- Python DocBlock Package – для Atom.
- Autodoc – для PyCharm.
В PyCharm существует встроенный функционал добавления документации к коду. Для этого нужно:
- Переместить курсор под объявление функции.
- Написать тройные кавычки «»» и нажмите » Enter» .