Документировать код скучно, так зачем тратить время?
Хорошая документация, нравится нам это или нет, абсолютно критична для успеха проекта. Так почему же так сложно найти хорошую документацию?
В действительности им часто пренебрегают и рассматривают как продукт худшего качества по сравнению с самим кодом. Создание хорошей, удобной документации занимает слишком много времени.
Это пренебрежение - ошибка. Принятие инструментов, которые мы создаем, во многом зависит от простоты использования. Если документация сложна для навигации, понимания или просто непривлекательна для использования, пользователям она не понравится.
Имея это в виду, я считаю, что мы все должны стремиться к упрощенной, но всеобъемлющей документации.
Мы расскажем, как быстро создать инструмент автоматической документации для любого проекта Python. Благодаря этому документация может быть создана для сложных проектов с огромным объемом кода всего за несколько секунд.
Регулярные выражения (регулярное выражение) - наш друг, мы будем в значительной степени полагаться на них на протяжении всей статьи. В этой статье я намерен по-настоящему сосредоточиться на этом аспекте кода, и я надеюсь, что вы многому научитесь (я определенно знаю).
Если вы используете стандарты строк документации NumPy / SciPy, вы даже можете использовать точный код, который я использую, который доступен на GitHub здесь!
Перед началом работы доступны готовые инструменты документации. Нет ничего плохого в их использовании, вообще-то Сфинкс, единственный, который я использовал всесторонне, на самом деле очень хорош.
Однако пока у меня нет других инструментов, которые бы позволяли быстро создавать документацию в том стиле, который мне нравится.
Тем не менее, они хороши, и я советую вам рассмотреть все ваши варианты. Если вам нравится инструмент Sphinx (или другой), воспользуйтесь им. Если вы хотите создать что-то в своем стиле (и изучить регулярное выражение), используйте это руководство.
Состав
Структуру документации autodocs можно представить в виде дерева, например:
Из этого мы видим, что есть несколько ключевых структур, которые мы должны извлечь из нашего кода, а именно:
- Строки документации: (содержащие описания, параметры, параметр dtype и т. д.) - это разделы цитат, которые мы используем для описания каждого скрипта, класса и функции.
- Классы: сами определения классов, их описания (содержащиеся в строке документации) и методы.
- Функции: сами функции вместе со строками документации, которые содержат описание функции и параметры.
- Параметры: содержатся в строке документации функции. Каждый параметр содержит описание, тип данных и «необязательный» флаг.
Мы возьмем каждый из этих компонентов и их соответствующие подкомпоненты и используем эту информацию для разработки другой древовидной структуры упрощенной, понятной документации.
Строки документации
Как видите, мы можем идентифицировать строки документации по тройным кавычкам, заключенным вокруг каждой. В регулярном выражении мы пишем (?s)\"{3}.*?\"{3}
.
Здесь происходит несколько вещей.
\
используется как экранирующий символ. Мы помещаем его перед символами, имеющими другие значения в Python"
или регулярном выражении{[()]}
, чтобы «избежать» их исходного значения. Это означает, что мы просто хотим найти этого персонажа.- У нас есть два набора
\"{3}
, это просто соответствует двум имеющимся у нас наборам тройных кавычек. .*
соответствует любому символу, кроме символов новой строки,?
делает этот квантификатор «ленивым» - останавливает регулярное выражение, сопоставляющее самый первый экземпляр"""
с самым последним экземпляром"""
, вместо этого мы сопоставляем первый и второй, третий и четвертый и т. Д.(?s)
- это глобальный модификатор шаблона,s
сообщает регулярному выражению, что мы хотим, чтобы.*
также соответствовал символам новой строки.
Классы
Начало класса можно легко определить по слову class
, за которым следует пробел, несколько букв и двоеточие.
Чтобы предотвратить сопоставление всего, что находится после начала класса, мы определяем конец класса как первое место, где за новой строкой непосредственно следует текстовый символ.
В регулярном выражении мы пишем (?sm)class [\w\d_]+:.*(^\w)
.
[\w\d_]
соответствует тексту, цифрам и символам подчеркивания, добавление+
означает, что он будет соответствовать неограниченному количеству этих символов.(?sm)
снова является нашим глобальным модификатором шаблона, флагm
сообщает регулярному выражению, что мы хотим, чтобы^
и$
обозначали начало и конец строки соответственно.(^.)
- начало строки^
, за которой сразу следует любой символ.
.
Функции
Функции немного другие. В отличие от классов, где мы фиксируем все, что содержится в классе (чтобы мы могли также извлекать методы класса), нам нужны только имя функции и строка документации.
К счастью, это значительно упрощает нашу первую функцию, сопоставляющую регулярное выражение, мы пишем (?s)def [\w\d_]+\(.*?\):\s+\"{3}.*?\"{3}
.
Описания
Каждая функция также содержит имя, описание и список параметров, которые мы должны извлечь. Мы делаем это по частям, извлечение параметров немного сложнее, и мы рассмотрим дальше.
Имя функции мы извлекаем с помощью def [\w\d_]+\(
, с помощью которого мы удаляем def
и (
, оставляя только имя функции.
name = re.search(r"def [\w\d_+\(", func).group() name = name.replace("def ", "").replace("(", "").strip()
Описание desc
и список параметров params
извлекаются путем извлечения строки документации с (?s)\"{3}.*?\"{3}
и разделения на Parameters\n
. Затем мы удаляем тройные кавычки и лишние пробелы.
docstring = re.search(r"(?s)\"{3}.*\"{3}").group()
desc, params = docstring.split("Parameters\n")
desc = re.sub(r"\s+", " ", desc.replace('"""'
, "")).strip()
Параметры
У нас есть наш список параметров, содержащийся в params
, который выглядит так:
Чтобы соответствовать каждому параметру, мы пишем (?s)\w+ : .*?(?=\w+ :)
.
По большей части мы уже рассмотрели здесь все, кроме одной части - (?=\w+ :)
.
Здесь мы знаем, что \w+ :
будет соответствовать нескольким буквам, за которыми следует пробел и двоеточие. Однако он завернут в (?=)
. Это называется утверждением положительного просмотра вперед.
Добавляя это, мы сообщаем регулярному выражению assert (?=)
, что за тем, что мы сопоставили, сразу следует \w :
.
Если мы поместим это в онлайн-тестер регулярных выражений, мы увидим, что он работает почти идеально, но, к сожалению, пропускает последний параметр (так как за ним не следует \w :
).
Мы решаем эту проблему, извлекая по одному параметру за раз. В этом цикле, если мы обнаруживаем, что никакие параметры не были найдены, мы пробуем еще раз с измененным параметром, находя регулярное выражение (?s)\w+ : .*
, извлекая последний параметр.
А подробности о параметрах?
Наш последний, самый глубокий уровень извлечения требует, чтобы мы извлекли имя параметра, тип данных, «необязательный» флаг и описание.
Для этого мы просто разделяем текст нашего параметра new_param
на новые строки \n
. Теперь в индексе 0
у нас есть первая строка, содержащая имя параметра, тип данных и, возможно, необязательный флаг. Давайте изменим приведенный выше код, чтобы включить это извлечение.
Единственная дополнительная логика здесь - это разделение первой строки new_param
на :
, что дает нам name
и dtype
. Затем следует проверка на слово 'optional'
в dtype
, что дает нам optional
.
Затем мы просто добавляем эти сведения о параметрах в новый словарь params
. Что-то вроде:
Наш Кодекс
На этом мы закончили с кодом, который извлечет из нашего кода все, что нам нужно. Мы создали древовидную структуру, о которой мы говорили ранее:
Все, что содержится в строке скриптов или классов, будет выделено на специальную страницу документации. Страница скрипта будет содержать имя, описание и ссылку на любые классы под ним.
При создании документации мы будем перебирать все сценарии и классы, содержащиеся в проекте. Итеративно строим документацию для каждого.
Таким образом, мы можем перейти к созданию HTML-кода для наших документов.
Создание документов
Документы разделены на четыре основных раздела:
- информация о скрипте / классе. Он отображается прямо вверху каждой страницы. Это информация из самого первого скрипта или строки документации уровня класса.
- Навигационная панель хлебных крошек. Позволяя нам перемещаться вверх и вниз по уровням документации.
- Раздел Классы. Здесь у нас будет список классов скриптов, нажатие на кнопку класса отобразит описание класса и ссылку на документацию.
- Раздел Функции. Он содержит список функций с подробным описанием их соответствующих параметров.
Перед динамическим построением этих разделов нам действительно нужно добавить <head>
информацию на нашу исходную HTML-страницу, пока мы будем делать ее очень простой.
Мы будем использовать Bootstrap для CSS и JS на нашей странице. Внутри <head>
мы прикрепляем таблицу стилей Bootstrap CSS с помощью элемента <link>
. Скрипты JS добавляются позже.
В Python мы можем либо добавить этот код в блок с тройными кавычками, либо прочитать его из файла. Переменная html
изначально будет просто содержать этот элемент <head>
.
Информация верхнего уровня
Это информация о нашем скрипте / классе, которая отображается вверху нашей страницы.
Поскольку мы перебираем каждый скрипт и класс независимо, мы можем относиться к ним одинаково. Единственное различие между ними состоит в том, что страницы скриптов будут содержать разделы классов, а классы - нет (если у вас нет класса внутри класса).
Для docs.py
fullpath
будет содержать только одну запись ['docs']
. Но для более низких уровней, таких как DocsBuilder
, он будет содержать список уровней, ведущих к классу ['docs', 'DocsBuilder']
. Таким образом, наш основной заголовок является последним индексом в списке fullpath[-1]
.
Описание верхнего уровня содержится в desc
.
Навигация по хлебным крошкам
Далее идет наш раздел навигации, мы инициализируем его страницей самого высокого уровня readme
.
После этого мы перебираем элементы в fullpath
, чтобы создать полный «хлебный путь» к нашей текущей странице.
Поскольку создаваемые имена файлов будут иметь верблюжий регистр и включать любые предшествующие классы / скрипты. Мы применяем такое же форматирование к переменной path
. Это делает DocsBuilder -> docs.docsbuilder
.
Классы
Все классы хранятся в словаре с творческим названием classes
. Этот словарь содержит пары ключ-значение имя класса и описание.
Сначала мы проверяем, что текущий уровень содержит классы, в противном случае мы не добавляем раздел классов.
Если у нас есть классы, мы добавляем начало раздела классов и кнопок классов. Затем мы перебираем classes
, добавляя кнопку для каждого элемента, содержащего только класс name
.
Затем мы добавляем конец кнопок класса и начало описаний классов (и ссылок). Мы перебираем каждый элемент, добавляя содержимое кнопки. Это позволяет нам отображать описание класса и ссылку при нажатии на кнопку.
Наконец, мы заканчиваем раздел класса.
Функции
Функции хранятся в словаре под названием funcs
, который отформатирован, как указано выше.
Здесь происходит намного больше из-за дополнительного уровня параметров внутри каждой функции.
Все функции содержатся в неупорядоченном списке <ul>
, где каждый элемент списка <li>
состоит из одной функции.
Сначала мы добавляем функцию name
, а затем пример функции, содержащийся в элементе <kbd>
, построенный путем объединения всех параметров с ","
.
За ними следует таблица параметров. Здесь мы инициализируем начало таблицы тегами <table>
и <tbody>
. Цикл строит таблицу построчно, состоящую из имени и описания параметра.
После этого идут несколько закрывающих секций тегов. Поскольку раздел функций - это наша последняя динамически создаваемая часть страницы, мы можем добавить скрипты Bootstrap JS и закрывающие теги.
Создание документов
После того, как мы объединили все эти сценарии извлечения кода и сборки страниц, мы можем создавать документацию с помощью всего нескольких строк кода.
Здесь, конечно, все, что вы видели на протяжении всей статьи, было встроено в класс DocsBuilder
.
Все отработанные нами регулярные выражения извлечения содержатся в методе extract
. Строительный код HTML в методе build
.
Я запускал одни и те же сценарии в гораздо более крупных проектах, содержащих множество сценариев и классов. Тем не менее, вся документация создается мгновенно.
Вы можете найти весь код в репозитории autodocs на GitHub. Это ни в коем случае не законченный проект, поэтому не стесняйтесь предлагать улучшения или указывать на ненужный код!
Если у вас есть другие вопросы или предложения, не стесняйтесь обращаться через Twitter или в комментариях ниже.
Спасибо за прочтение!
Если вам понравилась статья, возможно, вас заинтересует другая недавно написанная мной статья об обработке текстовых данных из Интернета. С акцентом на подготовку данных для машинного обучения: