Documenting Python Code and Projects
Зачем вам нужно документировать свой код на Python? Что должна включать ваша проектная документация? Как вы пишете и генерируете документацию?
Документация является важной частью разработки программного обеспечения. Без надлежащей документации внутренним и внешним заинтересованным сторонам может быть очень сложно или даже невозможно использовать и/или поддерживать ваш код. Это также значительно затрудняет привлечение новых разработчиков. Если сделать еще один шаг вперед, то без культуры документирования и обучения в целом вы будете часто совершать одни и те же ошибки снова и снова. К сожалению, многие разработчики относятся к документации как к чему-то второстепенному - к чему-то, что можно посыпать, например, черным перцем, без особого внимания.
В этой статье рассматривается, почему вы должны документировать свой код на Python и как это сделать.
Полное руководство по Python:
- Современные среды Python - управление зависимостями и рабочим пространством
- Тестирование на Python
- Современная разработка на основе тестирования на Python
- Качество кода на Python
- Проверка типа Python
- Документирование кода и проектов на Python (эта статья!)
- Рабочий процесс проекта на Python
Содержимое
- Комментарии против документации
- Строки документации
- Sphinx
- Документация по API
- Тесты как документация
- Документирование REST API Flask
- Заключение
Комментарии против документации
В чем разница между комментариями к коду и документацией?
Документация - это отдельный ресурс, который помогает другим пользователям использовать ваш API, пакет, библиотеку или фреймворк без необходимости читать исходный код. С другой стороны, комментарии предназначены для разработчиков, которые читают ваш исходный код. Документация - это то, что должно присутствовать всегда, но этого нельзя сказать о комментариях. Их наличие приятно, но не обязательно. Документация должна указывать другим пользователям как и когда что-либо использовать, а комментарии должны отвечать на почему вопросы:
- Почему это делается именно так?
- Почему это здесь, а не там?
На какие вопросы затем должен ответить ваш чистый код:
- Что это?
- Что делает этот метод?
| Type | Answers | Stakeholder |
|---|---|---|
| Documentation | When and How | Users |
| Code Comments | Why | Developers |
| Clean Code | What | Developers |
Строки документации
Как указано в PEP-257, строка документации Python (или docstring) - это специальный "строковый литерал", который встречается как первая инструкция в определении модуля, функции, класса или метода" для формирования атрибута __doc__ данного объекта. Это позволяет вам встраивать документацию непосредственно в ваш исходный код.
Например, предположим, что у вас есть модуль с именем temperature.py с единственной функцией, которая вычисляет среднесуточные температуры. Используя docstrings, вы можете задокументировать это следующим образом:
"""
The temperature module: Manipulate your temperature easily
Easily calculate daily average temperature
"""
from typing import List
class HighTemperature:
"""Class representing very high temperatures"""
def __init__(self, value: float):
"""
:param value: value of temperature
"""
self.value = value
def daily_average(temperatures: List[float]) -> float:
"""
Get average daily temperature
Calculate average temperature from multiple measurements
:param temperatures: list of temperatures
:return: average temperature
"""
return sum(temperatures)/len(temperatures)
Вы можете просмотреть строки документации, указанные для функции daily_average, обратившись к ее атрибуту __doc__:
>>> from temperature import daily_average
>>>
>>> print(daily_average.__doc__)
Get average daily temperature
:param temperatures: list of temperatures
:return: average temperature
Вы также можете просмотреть всю документацию на уровне модуля, используя встроенную функцию справка:
>>> import temperature
>>>
>>> help(temperature)
Стоит отметить, что вы можете использовать функцию help со встроенными ключевыми словами (int, float, def и так далее), классами, функциями и модулями.
Однострочный против многострочного
Строки документации могут быть однострочными или многострочными. В любом случае первая строка всегда рассматривается как краткое изложение. Строка сводки может использоваться инструментами автоматической индексации, поэтому важно, чтобы она помещалась в одной строке. При использовании однострочных строк документации все должно быть в одной строке: начальные кавычки, итоговые и заключительные кавычки.
class HighTemperature:
"""Class representing very high temperatures"""
# code starts here
При использовании многострочных строк документации структура выглядит следующим образом: вводные кавычки, краткое содержание, пустая строка, более подробное описание и заключительные кавычки.
def daily_average(temperatures: List[float]) -> float:
"""
Get average daily temperature
Calculate average temperature from multiple measurements
:param temperatures: list of temperatures
:return: average temperature
"""
return sum(temperatures) / len(temperatures)
Помимо описания того, что делает конкретная функция, класс, метод или модуль, вы также можете указать:
- аргументы функции
- функция возвращает
- атрибуты класса
- возникали ошибки
- ограничения
- примеры кода
Форматы
Существуют четыре наиболее распространенных формата:
Выберите тот, который подходит вам больше всего, и придерживайтесь его на протяжении всего проекта.
Используя docstrings, вы можете четко выразить свои намерения на разговорном языке, чтобы помочь другим (и самому себе в будущем!) лучше понять, когда, где и как использовать определенный код.
Ворсинка
Строки документации можно компоновать так же, как и в вашем коде. Средства компоновки обеспечивают правильный формат строк документации и их соответствие фактической реализации, что помогает сохранять документацию свежей.
Darglint - популярный инструмент для компоновки документации на Python.
$ pip install darglint
Давайте добавим temperature.py модуль:
def daily_average(temperatures: List[float]) -> float:
"""
Get average daily temperature
Calculate average temperature from multiple measurements
:param temperatures: list of temperatures
:return: average temperature
"""
return sum(temperatures) / len(temperatures)
Ворсинок:
$ darglint --docstring-style sphinx temperature.py
Что произойдет, если вы измените название параметра с temperatures на temperatures_list?
$ darglint --docstring-style sphinx temperature.py
temperature.py:daily_average:27: DAR102: + temperatures
temperature.py:daily_average:27: DAR101: - temperatures_list
Примеры кода
Вы также можете добавить примеры кода в строки документации, показывающие пример использования функции, метода или класса.
Например:
def daily_average(temperatures: List[float], new_param=None) -> float:
"""
Get average daily temperature
Calculate average temperature from multiple measurements
>>> daily_average([10.0, 12.0, 14.0])
12.0
:param temperatures: list of temperatures
:return: Average temperature
"""
return sum(temperatures)/len(temperatures)
Примеры кода также могут быть выполнены с помощью pytest, как и любой другой тест с помощью doctest. Наряду с редактированием, это также помогает гарантировать, что ваша документация остается свежей и синхронизированной с кодом.
Ознакомьтесь с doctest — тестирование с помощью документации для получения дополнительной информации
doctest.
Итак, в приведенном выше примере pytest будет утверждать, что daily_average([10.0, 12.0, 14.0]) равно 12.0. Чтобы запустить этот пример кода в качестве теста, вам просто нужно запустить pytest с параметром doctest-modules:
$ python -m pytest --doctest-modules temperature.py
=============================== test session starts ===============================
platform darwin -- Python 3.11.0, pytest-7.2.1, pluggy-1.0.0
rootdir: /Users/michael/repos/testdriven/documenting-python
collected 1 item
temperature.py . [100%]
================================ 1 passed in 0.01s ================================
Что произойдет, если вы измените пример кода на:
>>> daily_average([10.0, 12.0, 14.0])
13.0
$ python -m pytest --doctest-modules temperature.py
=============================== test session starts ===============================
platform darwin -- Python 3.11.0, pytest-7.2.1, pluggy-1.0.0
rootdir: /Users/michael/repos/testdriven/documenting-python
collected 1 item
temperature.py F [100%]
==================================== FAILURES =====================================
_______________________ [doctest] temperature.daily_average _______________________
022
023 Get average daily temperature
024
025 Calculate average temperature from multiple measurements
026
027 >>> daily_average([10.0, 12.0, 14.0])
Expected:
13.0
Got:
12.0
/Users/michael/repos/testdriven/documenting-python/temperature.py:27: DocTestFailure
============================= short test summary info =============================
FAILED temperature.py::temperature.daily_average
================================ 1 failed in 0.01s ================================
Подробнее о pytest читайте в статье Тестирование на Python.
Сфинкс
Добавлять строки документации в свой код - это здорово, но вам все равно нужно представить это своим пользователям.
Здесь используются такие инструменты, как Sphinx, Epydoc и MkDocs вступайте в игру, которая преобразует строки документации вашего проекта в HTML и CSS.
Сфинкс, безусловно, самый популярный из них. Он используется для создания документации для ряда проектов с открытым исходным кодом, таких как Python и Flask. Это также один из инструментов документирования, поддерживаемых Read the Docs, который используется тысячами проектов с открытым исходным кодом, таких как Requests., Flake8 и pytest и это лишь некоторые из них.
Давайте посмотрим на это в действии. Для начала следуйте официальному руководству по загрузке и установке Sphinx.
$ sphinx-quickstart --version
sphinx-quickstart 6.1.3
Создайте новый каталог проекта:
$ mkdir sphinx_example
$ cd sphinx_example
Затем добавьте новый файл с именем temperature.py:
"""
The temperature module: Manipulate your temperature easily
Easily calculate daily average temperature
"""
from typing import List
class HighTemperature:
"""Class representing very high temperatures"""
def __init__(self, value: float):
"""
:param value: value of temperature
"""
self.value = value
def daily_average(temperatures: List[float]) -> float:
"""
Get average daily temperature
:param temperatures: list of temperatures
:return: average temperature
"""
return sum(temperatures)/len(temperatures)
Чтобы выделить файлы и папки для Sphinx и создать документацию для temperature.py в корне проекта выполните следующее:
$ sphinx-quickstart docs
Вам будет предложено ответить на несколько вопросов:
> Separate source and build directories (y/n) [n]: n
> Project name: Temperature
> Author name(s): Your Name
> Project release []: 1.0.0
> Project language [en]: en
После этого каталог "документы" должен содержать следующие файлы и папки:
docs
├── Makefile
├── _build
├── _static
├── _templates
├── conf.py
├── index.rst
└── make.bat
Далее давайте обновим конфигурацию проекта. Откройте docs/conf.py и добавьте сверху следующее:
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
Теперь autodoc, который используется для извлечения документации из docstrings, будет искать модули в родительской папке "docs".
Добавьте следующие расширения в список extensions:
extensions = [
'sphinx.ext.autodoc',
]
Откройте docs/index.rst и отредактируйте его так, чтобы он выглядел следующим образом:
Welcome to Temperature documentation!
=====================================
.. automodule:: temperature
:members:
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
Содержимое index.rst записано в reStructuredText, который представляет собой формат файла для текстовых данных, аналогичный Markdown, но гораздо более мощный, поскольку предназначен для написания технической документации.
Примечания:
- Заголовки создаются путем подчеркивания (и необязательно наложения) заголовка символом
=, по крайней мере, такой же длины, как текст: - Директива automodule используется для сбора строк документации из модулей Python. Итак,
.. automodule:: temperatureсообщает Sphinx собрать строки документации из модуля temperature.py . - Директивы
genindex,modindex, иsearchиспользуются для создания общего индекса, индекса документированных модулей, и страница поиска, соответственно.
Из каталога "docs" создайте документацию:
$ make html
Откройте docs/_build/html/index.html в вашем браузере. Вы должны увидеть:

Теперь вы можете загружать документы самостоятельно, используя инструмент, подобный Netlify, или с помощью сервиса, подобного Прочитайте документы.
Документация по API
Говоря о документации, не забывайте о документации для ваших API. У вас есть конечные точки с их URL-адресами, параметрами URL-адресов, параметрами запроса, кодами состояния, текстами запросов и текстами ответов. Даже простой API может иметь ряд параметров, которые трудно запомнить.
Спецификация OpenAPI (ранее - спецификация Swagger) предоставляет стандартный формат для описания, создания, использования и визуализации RESTful API. Спецификация используется для создания документации с помощью Swagger UI или ReDoc. Его также можно импортировать в такие инструменты, как Postman. Вы также можете создавать разделительные заглушки и клиентские SDK-пакеты с помощью таких инструментов, как Swagger Codegen и OpenAPI Generator.
Полный список редакторов, линтеров, анализаторов, генераторов кода, документации, инструментов тестирования и проверки схем/данных для OpenAPI приведен в разделе Инструменты OpenAPI.
Сама спецификация должна быть написана либо в YAML, либо в JSON. Например:
---
openapi: 3.0.2
info:
title: Swagger Petstore - OpenAPI 3.0
description: |-
This is a sample Open API
version: 1.0.0
servers:
- url: "/api/v3"
paths:
"/pet":
post:
summary: Add a new pet to the store
description: Add a new pet to the store
operationId: addPet
requestBody:
description: Create a new pet in the store
content:
application/json:
schema:
"$ref": "#/components/schemas/Pet"
required: true
responses:
'200':
description: Successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/Pet"
'405':
description: Invalid input
components:
schemas:
Pet:
required:
- name
- photoUrls
type: object
properties:
id:
type: integer
format: int64
example: 10
name:
type: string
example: doggie
photoUrls:
type: array
items:
type: string
status:
type: string
description: pet status in the store
enum:
- available
- pending
- sold
requestBodies:
Pet:
description: Pet object that needs to be added to the store
content:
application/json:
schema:
"$ref": "#/components/schemas/Pet"
Писать такую схему вручную очень скучно и часто приводит к ошибкам. К счастью, существует ряд инструментов, которые помогают автоматизировать этот процесс:
- Джанго - drf-ясг, drf-захватывающий
- Колба - Колба-RESTX, Связь, Опока-арматура
- Встроена поддержка FastAPI - OpenAPI
Тесты в виде документации
До сих пор мы говорили о документации для пользователей (проектная документация) и разработчиков (комментарии к коду). Другой тип документации для разработчиков связан с самими тестами.
Как разработчик, работающий над проектом, вы должны знать больше, чем просто как использовать тот или иной метод. Вам нужно знать, работает ли он так, как ожидалось, и как его использовать для дальнейшей разработки. Хотя добавление примеров кода в строки документации может помочь в этом, такие примеры предназначены не для чего иного, как для простых примеров. Вам нужно добавить тесты, которые охватывают не только путь выполнения функции.
Тесты документируют три вещи:
- Каков ожидаемый результат при заданных входных данных
- Как обрабатываются пути к исключениям
- Как использовать данную функцию, метод или класс
Когда вы пишете тесты, обязательно используйте правильные названия и четко указывайте, что именно вы тестируете. Это значительно облегчит разработчику просмотр набора тестов, чтобы выяснить, как следует использовать ту или иную функцию или метод.
Более того, при написании теста вы в основном определяете, что должно входить в ваши документы. Структура , ЗАДАННАЯ, КОГДА, ЗАТЕМ, может быть легко преобразована в строки документации функции.
Например:
- ПРИВЕДЕН список измерений температуры ->;
:param temperatures: list of temperatures - ПРИ вызове 'daily_average' ->
>>> daily_average([10.0, 12.0, 14.0]) - ЗАТЕМ возвращается средняя температура ->
Get average temperature, :return: Average temperature
def daily_average(temperatures: List[float]) -> float:
"""
Get average temperature
Calculate average temperature from multiple measurements
>>> daily_average([10.0, 12.0, 14.0])
12.0
:param temperatures: list of temperatures
:return: Average temperature
"""
return sum(temperatures)/len(temperatures)
Итак, вы можете рассматривать Разработку на основе тестирования (TDD) как форму разработки на основе документации, создав свои строки документации в виде кода:
- Написать тест
- Убедиться, что тест не пройден
- Написать код
- Убедитесь, что тест прошел успешно
- Выполните рефакторинг и добавьте строки документации
Подробнее о TDD читайте в статье Современная разработка на основе тестирования на Python.
Документирование REST API Flask
До сих пор мы рассматривали только теорию, поэтому давайте перейдем к реальному примеру. Мы создадим RESTful API с Flask для измерения температуры. Каждое измерение будет иметь следующие атрибуты: временная метка, температура, примечания. Flask-RESTX будет использоваться для автоматической генерации спецификации OpenAPI.
Итак, давайте начнем. Сначала создайте новую папку:
$ mkdir flask_temperature
$ cd flask_temperature
Затем инициализируйте свой проект с помощью Poetry:
$ poetry init
Package name [flask_temperature]:
Version [0.1.0]:
Description []:
Author [Your name <[email protected]>, n to skip]:
License []:
Compatible Python versions [^3.11]:
Would you like to define your main dependencies interactively? (yes/no) [yes] no
Would you like to define your development dependencies interactively? (yes/no) [yes] no
Do you confirm generation? (yes/no) [yes]
После этого добавьте Flask и Flask-RESTX:
$ poetry add flask flask-restx
Теперь давайте создадим наш документированный API. Добавьте файл для приложения Flask с именем app.py:
import uuid
from flask import Flask, request
from flask_restx import Api, Resource
app = Flask(__name__)
api = Api(app)
measurements = []
@api.route('/measurements')
class Measurement(Resource):
def get(self):
return measurements
def post(self):
measurement = {
'id': str(uuid.uuid4()),
'timestamp': request.json['timestamp'],
'temperature': request.json['temperature'],
'notes': request.json.get('notes'),
}
measurements.append(measurement)
return measurement
if __name__ == '__main__':
app.run()
Flask-RESTX использует представления на основе классов для организации ресурсов, маршрутов и методов HTTP. В приведенном выше примере класс Measurement поддерживает методы HTTP GET и POST. Другие методы вернут ошибку MethodNotAllowed. Flask-RESTX также сгенерирует схему OpenAPI при запуске приложения.
$ python app.py
Вы можете ознакомиться со схемой по адресу http://localhost:5000/swagger.json . Вы также сможете просмотреть доступный для просмотра API по адресу http://localhost:5000.

В настоящее время схема содержит только конечные точки. Мы можем определить текст запроса и ответа, чтобы сообщить нашим пользователям, что от них ожидается, а также что будет возвращено.
Обновить app.py:
import uuid
from flask import Flask, request
from flask_restx import Api, Resource, fields
app = Flask(__name__)
api = Api(app)
measurements = []
add_measurement_request_body = api.model(
'AddMeasurementRequestBody', {
'timestamp': fields.Integer(
description='Timestamp of measurement',
required=True,
example=1606509272
),
'temperature': fields.Float(
description='Measured temperature',
required=True, example=22.3),
'notes': fields.String(
description='Additional notes',
required=False, example='Strange day'),
}
)
measurement_model = api.model(
'Measurement', {
'id': fields.String(
description='Unique ID',
required=False,
example='354e405c-136f-4e03-b5ce-5f92e3ed3ff8'
),
'timestamp': fields.Integer(
description='Timestamp of measurement',
required=True,
example=1606509272
),
'temperature': fields.Float(
description='Measured temperature',
required=True,
example=22.3
),
'notes': fields.String(
description='Additional notes',
required=True,
example='Strange day'
),
}
)
@api.route('/measurements')
class Measurement(Resource):
@api.doc(model=[measurement_model])
def get(self):
return measurements
@api.doc(model=[measurement_model], body=add_measurement_request_body)
def post(self):
measurement = {
'id': str(uuid.uuid4()),
'timestamp': request.json['timestamp'],
'temperature': request.json['temperature'],
'notes': request.json.get('notes'),
}
measurements.append(measurement)
return measurement
if __name__ == '__main__':
app.run()
Чтобы определить модели для наших ответов и запросов, мы использовали api.model. Мы определили названия и соответствующие поля. Для каждого поля мы определили тип, описание, пример и то, требуется ли оно.

Чтобы добавить модели в конечные точки, мы использовали декоратор @api.doc. Параметр body определяет текст запроса, в то время как параметр model определяет текст ответа.

Теперь у вас должно быть общее представление о том, как документировать ваш Flask RESTful API с помощью Flask-RESTx. Это только начало. Ознакомьтесь с Документацией Swagger для получения подробной информации о том, как определить информацию для авторизации, параметры URL, коды состояния и многое другое.
Заключение
Большинство из нас, если не все, могут лучше справляться с написанием документации. К счастью, существует множество инструментов, упрощающих процесс ее написания. При написании пакетов и библиотек используйте Sphinx для организации и создания документации на основе строк документации. При работе с RESTful API используйте инструмент, который генерирует схему OpenAPI, поскольку эта схема может использоваться множеством инструментов - от средств проверки данных до генераторов кода. Ищете вдохновение? Stripe, Flask, Cypress и FastAPI являются отличными примерами хорошо выполненной документации.
Back to TopПолное руководство по Python:
- Современные среды Python - управление зависимостями и рабочим пространством
- Тестирование на Python
- Современная разработка на основе тестирования на Python
- Качество кода на Python
- Проверка типа Python
- Документирование кода и проектов на Python (эта статья!)
- Рабочий процесс проекта на Python