Документировать код скучно, так зачем тратить время?

Хорошая документация, нравится нам это или нет, абсолютно критична для успеха проекта. Так почему же так сложно найти хорошую документацию?

В действительности им часто пренебрегают и рассматривают как продукт худшего качества по сравнению с самим кодом. Создание хорошей, удобной документации занимает слишком много времени.

Это пренебрежение - ошибка. Принятие инструментов, которые мы создаем, во многом зависит от простоты использования. Если документация сложна для навигации, понимания или просто непривлекательна для использования, пользователям она не понравится.

Имея это в виду, я считаю, что мы все должны стремиться к упрощенной, но всеобъемлющей документации.

Мы расскажем, как быстро создать инструмент автоматической документации для любого проекта 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 или в комментариях ниже.

Спасибо за прочтение!

Если вам понравилась статья, возможно, вас заинтересует другая недавно написанная мной статья об обработке текстовых данных из Интернета. С акцентом на подготовку данных для машинного обучения: