DevGang
Авторизоваться

Подсказки типов и строки документации Python

В этой статье вы будете сопровождать меня в путешествии по автоматической генерации строк документации в стиле Google из подсказок типов Python. Мы рассмотрим следующие элементы.

  1. Подсказки типов Python
  2. Вставка подсказки типа в строки документации функции
  3. Автоматизация с помощью хуков Git перед фиксацией

Подсказки типа Python

Начиная с Python 3.5+, мы увидели следующее поколение документации по коду: намеки на типы переменных в аргументах функций/классов и операторах возврата. Это позволяет программам форматирования, линтерам и IDE обеспечивать поддержку проверки типов во время выполнения.

Пример подсказок типа Python
Пример подсказок типа Python

Зачем нужны подсказки

Проще говоря, типовые подсказки улучшают документацию исходного кода и читаемость. Они являются частью Python Enhancement Protocols (PEP), эволюционной структуры для повышения ясности и логики кода Python.

Как использовать подсказки типа

Подсказки можно импортировать из модуля typing. Формат <variable name> : <variable type> и может быть непосредственно вставлен в аргументы функции. Вы можете включить оператор возврата с расширением -> <return variable>.

import pandas as pd
from typing import Dict, List, Optional, Sequence, Tuple, Union

class DataProcessor:
    """Read and process data."""
    
    @classmethod
    def train_test_sets(
        cls,
        train_paths: Sequence[Union[Path, str]],
        test_paths: Optional[Sequence[Union[Path, str]]] = None,
        duplicate_threshold: int = 3,
    ) -> Tuple[pd.DataFrame, pd.DataFrame]:
        """Create train and test Pandas DataFrames while avoiding leakage-flow in train/test."""
        train_df: pd.DataFrame = DataProcessor.concatenate_datasets(train_paths)
            
        test_df: Optional[pd.DataFrame] = None
        if test_paths:
            test_df = DataProcessor.concatenate_datasets(test_paths)
        
        return train_df, test_df

Проверка типов

Обратите внимание, что подсказки типов не являются обязательными, и поэтому интерпретатор Python полностью игнорирует их (код Python с неправильно отформатированными подсказками типов все еще выполняется). Однако такие IDE, как Pycharm, уже интегрируют проверку типов, а инструмент статической проверки типов, такой как mypy, вычерпывает ошибки типа как баги.

Проверка типов в PyCharm
Проверка типов в PyCharm

Вставить подсказки типа в строки документации функции

Чтобы извлечь аргументы и их подсказки по типу из определений функций, мы собираемся

  1. использовать abstract syntax trees для анализа скриптов Python и чтения функций и их аргументов
  2. используйте библиотеку typing, чтобы получить подсказки типа переменных из аргументов нашей функции
  3. используйте regular expressions для сопоставления типа переменной с форматом строки документации в стиле Google
Автоматическое добавление типа переменной в строки документации в стиле Google
Автоматическое добавление типа переменной в строки документации в стиле Google

Абстрактные синтаксические деревья (AST)

Модуль ast помогает приложениям Python обрабатывать деревья абстрактного синтаксиса грамматики Python. Вместо того, чтобы читать скрипт Python в виде текстовых файлов, теперь мы можем программно читать грамматику через ast nodes. Полезный пакет для распечатки и понимания этих узлов - это astpretty. Мы используем модуль ast, чтобы получить имена функций или классов в скрипте.

import ast
import astpretty
import importlib


script = pathlib.Path("path_to_a_python_file")

with open(script, "r") as source:
    tree = ast.parse(source.read())

#  astpretty.pprint(tree)

for child in ast.iter_child_nodes(tree):
    if isinstance(child, (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)):
        if child.name not in ["main"]:

            docstring_node = child.body[0]
            module = importlib.import_module(script.stem)

            f_ = getattr(module, child.name)

Модуль pathlib.Path создает объектно-ориентированный путь в файловой системе, для которой вы можете запросить атрибуты, такие как .stem, последний компонент пути, без суффикса. Мы можем импортировать функции или классы в виде строки с расширениемimportlib.import_module.

getattr извлекает именованный атрибут из объекта, в данном случае из функции в нашем модуле Python, таким же образом, как и мы from module import attribute.

Печать

Метод get_type_hints() из модуля typing возвращает словарь, содержащий подсказки типа для функции, метода, модуля или объекта класса. get_type_hints не работает со строками, поэтому мы используем библиотеку ast для анализа методов из модуля.

from automate import docstring_from_type_hints

function_argument_type_hints = get_type_hints(docstring_from_type_hints)
function_return_argument_hint = type_hints.pop("return", None)

print(function_argument_type_hints)
>>> {'repo_dir': <class 'pathlib.Path'>, 'overwrite_script': <class 'bool'>, 'test': <class 'bool'>, 'return': <class 'str'>}

print(function_return_argument_hint)
>>> <class 'str'>

Последним key в этом словаре является «return» и включает в себя указание типа возвращаемой переменной, если оно есть, в противном случае - None. Мы можем исключить этот ключ из словаря с помощью метода .pop.

Регулярные выражения

Мы используем следующие регулярные выражения, чтобы понять, где аргументы функции определены в наших строках документации в стиле Google.

if type_hints:

    docstring = f'"""{ast.get_docstring(child, clean=True)}\n"""'
    docstring_lines = docstring.split("\n")

    if docstring:

        args = re.search(
            r'Args:(.*?)(Example[s]?:|Return[s]?:|""")',
            docstring,
            re.DOTALL,
        )

        new_arguments = {}
        if args:

            arguments = args.group()
            argument_lines = arguments.split("\n")

            exclude = [
                "Args:",
                "Example:",
                "Examples:",
                "Returns:",
                '"""',
            ]

            argument_lines = [arg for arg in argument_lines if arg]
            argument_lines = [arg for arg in argument_lines if not any(x in arg for x in exclude)]

            for argument in argument_lines:
                arg_name = argument.split()[0]
                if arg_name in argument:

                    if argument.split(":"):
                        if "(" and ")" in argument.split(":")[0]:

                            variable_type = str(type_hints[arg_name])
                            class_type = re.search(r"(<class ')(.*)('>)", variable_type)
                            if class_type:
                                variable_type = class_type.group(2)

                            new_argument_docstring = re.sub(
                                r"\(.*?\)",
                                f"({variable_type})",
                                argument,
                            )

                            idx = docstring_lines.index(f"{argument}")
                            new_arguments[idx] = f"{new_argument_docstring}"

Во- первых, мы get_docstring из нашего функционального узла и ищем три атрибута ArgsExample[s] и Return[s](строка 8). Затем мы перебираем каждый аргумент функции (строка 31), используем его для выбора значения в нашем словаре type_hints (строка 38), удаляем часть <class ''> напечатанного type_hint (строка 39) и вставляем его между круглыми скобками (строка 43).

Пожалуйста, посмотрите здесь полный сценарий, чтобы увидеть, как мы делаем то же самое для подсказок типа возвращаемых аргументов функции и перезаписываем наш скрипт Python нашим недавно отформатированным литералом строки документации.

Автоматизация с помощью хуков Git перед фиксацией

конвейер до фиксации Git
конвейер до фиксации Git

С помощью хуков Git, мы можем запустить линтинг и форматирование инструментов, таких как mypyautoflakeflake8isortblack перед каждой фиксацией и отправкой в наш репозиторий. Это позволяет нам автоматически создавать «стандарт кодирования» в нашем проекте Python. Эти перехватчики Git предоставляются пакетом pre-commit, который использует файл pre-commit-config.yaml, который указывает, какие пакеты включать в конвейер предварительной фиксации.

Помимо инструментов линтинга и форматирования, вы также можете включить выполнение скриптов Python в эти перехватчики Git с помощью команд bash, что позволяет нам автоматизировать эту функцию docstring_from_type_hints(). При этом при каждой фиксации подсказки типов вставляются в нашу строку документации.

Сделав еще один шаг, мы можем автоматически создавать красивую документацию в MkDocs со всеми функциями в наших скриптах Python.

Пример автоматически сгенерированных строк документов в стиле Google в MkDocs
Пример автоматически сгенерированных строк документов в стиле Google в MkDocs

Источник:

#Python #Data Science
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать

В подарок 100$ на счет при регистрации

Получить