Подсказки типов и строки документации Python
В этой статье вы будете сопровождать меня в путешествии по автоматической генерации строк документации в стиле Google из подсказок типов Python. Мы рассмотрим следующие элементы.
- Подсказки типов Python
- Вставка подсказки типа в строки документации функции
- Автоматизация с помощью хуков Git перед фиксацией
Подсказки типа Python
Начиная с Python 3.5+, мы увидели следующее поколение документации по коду: намеки на типы переменных в аргументах функций/классов и операторах возврата. Это позволяет программам форматирования, линтерам и IDE обеспечивать поддержку проверки типов во время выполнения.
Зачем нужны подсказки
Проще говоря, типовые подсказки улучшают документацию исходного кода и читаемость. Они являются частью 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
, вычерпывает ошибки типа как баги.
Вставить подсказки типа в строки документации функции
Чтобы извлечь аргументы и их подсказки по типу из определений функций, мы собираемся
- использовать
abstract syntax trees
для анализа скриптов Python и чтения функций и их аргументов - используйте библиотеку
typing
, чтобы получить подсказки типа переменных из аргументов нашей функции - используйте
regular expressions
для сопоставления типа переменной с форматом строки документации в стиле 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
из нашего функционального узла и ищем три атрибута Args
, Example[s]
и Return[s]
(строка 8). Затем мы перебираем каждый аргумент функции (строка 31), используем его для выбора значения в нашем словаре type_hints
(строка 38), удаляем часть <class ''>
напечатанного type_hint (строка 39) и вставляем его между круглыми скобками (строка 43).
Пожалуйста, посмотрите здесь полный сценарий, чтобы увидеть, как мы делаем то же самое для подсказок типа возвращаемых аргументов функции и перезаписываем наш скрипт Python нашим недавно отформатированным литералом строки документации.
Автоматизация с помощью хуков Git перед фиксацией
С помощью хуков Git, мы можем запустить линтинг и форматирование инструментов, таких как mypy
, autoflake
, flake8
, isort
, black
перед каждой фиксацией и отправкой в наш репозиторий. Это позволяет нам автоматически создавать «стандарт кодирования» в нашем проекте Python. Эти перехватчики Git предоставляются пакетом pre-commit
, который использует файл pre-commit-config.yaml
, который указывает, какие пакеты включать в конвейер предварительной фиксации.
Помимо инструментов линтинга и форматирования, вы также можете включить выполнение скриптов Python в эти перехватчики Git с помощью команд bash, что позволяет нам автоматизировать эту функцию docstring_from_type_hints()
. При этом при каждой фиксации подсказки типов вставляются в нашу строку документации.
Сделав еще один шаг, мы можем автоматически создавать красивую документацию в MkDocs со всеми функциями в наших скриптах Python.