Генератор строк документации в типе Google

Привет всем!

В этой задаче мне нужно было создать генератор строк документации. Мне просто интересно, как вы видите эту проблему, мне приходится «брать данные» из словаря

{'Args': None, 'Returns': None, 'Raises': None, 'Attributes': None, 'Summary': None, 'Description': None}

а затем сгенерируйте строку документации в правильном порядке.

Это мое решение:

import textwrap

def gendoc(config):
    docstring = []
    if ("Summary" in config) and (config["Summary"] is not None):
        docstring.append(config["Summary"])
    if ("Args" in config) and (config["Args"] is not None):
        args = ["Args:"] + [f"t{arg}: Function argument" for arg in config["Args"]]
        docstring += args
    if ("Attributes" in config) and (config["Attributes"] is not None):
        attr = ["Attributes:"] + [f"t{attr}: Information about parameter {attr}" for attr in config["Attributes"]]
        docstring += attr
    if ("Raises" in config) and (config["Raises"] is not None):
        raises = ["Raises: "] + [f"t {config.get('Raises')}"]
        docstring += raises
    if ("Returns" in config) and (config["Returns"] is not None):
        returns = ["Returns: "] + [f"t {config.get('Returns')}"]
        docstring += returns
    if ("Description" in config) and (config["Description"] is not None):
        value = config.get('Description')
        wrapper=textwrap.TextWrapper(subsequent_indent="t", width = 120)
        description = ["Description: "] + [f"t{wrapper.fill(value)}"]
        docstring += description
    if ("Todo" in config) and (config["Todo"] is not None):
        star = "*"
        todo = ["Todo:"] + [f"t{star} {todo}" for todo in config["Todo"]]
        docstring += todo
        
    docstring = "nn".join(docstring)
    return docstring

Мой вывод:

The Pear object describes the properties of pears.

Args:

    a: Function argument

    b: Function argument

Attributes:

    a: Information about parameter a

    b: Information about parameter b

Raises: 

     AttributeError: The ``Raises`` section is a list of all exceptions that are relevant to the interface.

Returns: 

     The return value. True for success, False otherwise.

Description: 

    The description may span multiple lines. Following lines should be indented. The type is optional. The description may
    span multiple lines. Following lines should be indented. The type is optional.The description may span multiple lines.
    Following lines should be indented. The type is optional.The description may span multiple lines. Following lines
    should be indented. The type is optional.

Todo:

    * Do something

    * Something else
 

Любые советы, как сделать это короче и аккуратнее, ближе к продвинутому или просто лучшему решению, будут определенно к делу. Заранее спасибо!

Хорошего дня!

2 ответа
2

  • По возможности избегайте представления внутренних данных в виде словарей. Как указывает @RootTwo, инициализировать класс данных из dict через ** оператор kwarg-splatting, если это необходимо.
  • Было бы неплохо поддерживать вывод в строку, стандартный вывод или файл через обертки. StringIO делает это легко.
  • Избегать t — имеет среднезависимый отступ; Лучше иметь рендеринг пространства с настраиваемым пользователем уровнем отступа.
  • Оберните все, а не только свое описание.
  • Это вопрос личного мнения, но я не согласен с двойным переводом строки внутри ваших разделов; резервирование двойных символов новой строки для разрывов разделов проясняет ситуацию.

Пример кода

from dataclasses import dataclass
from io import StringIO
from sys import stdout
from textwrap import TextWrapper
from typing import Optional, Tuple, Sequence, TextIO

Pair = Tuple[str, str]
PairSeq = Sequence[Pair]


@dataclass
class Documentation:
    summary: Optional[str] = None
    args: Optional[PairSeq] = None
    attrs: Optional[PairSeq] = None
    raises: Optional[PairSeq] = None
    returns: Optional[str] = None
    desc: Optional[str] = None
    todo: Optional[Sequence[str]] = None
    indent: int = 4
    wrap: int = 80

    def __post_init__(self):
        self.wrap_outer = TextWrapper(width=self.wrap).fill
        indent=" " * self.indent
        self.wrap_inner = TextWrapper(
            width=self.wrap,
            initial_indent=indent,
            subsequent_indent=indent,
        ).fill

    def __str__(self) -> str:
        with StringIO() as f:
            self.to_file(f)
            return f.getvalue()

    def print(self) -> None:
        self.to_file(stdout)

    def write_section(self, title: str, content: str, f: TextIO) -> None:
        if content:
            f.write(f'n{title}:n')
            f.write(self.wrap_inner(content) + 'n')

    def write_pairs(self, title: str, pairs: PairSeq, f: TextIO) -> None:
        if pairs:
            f.write(f'n{title}:n')
            f.writelines(
                self.wrap_inner(f'{name}: {desc}') + 'n'
                for name, desc in pairs
            )

    def to_file(self, f: TextIO) -> None:
        if self.summary:
            f.write(self.wrap_outer(self.summary))

        self.write_pairs('Arguments', self.args, f)
        self.write_pairs('Attributes', self.attrs, f)
        self.write_pairs('Raises', self.raises, f)
        self.write_section('Returns', self.returns, f)
        self.write_section('Description', self.desc, f)

        if self.todo:
            f.write(f'nTodo:n')
            f.writelines(
                self.wrap_inner(f'* {todo}') + 'n'
                for todo in self.todo
            )


def test():
    Documentation(
        summary='The Pear object describes the properties of pears.',
        args=(
            ('a', 'Function argument'),
            ('b', 'Function argument'),
        ),
        attrs=(
            ('a', 'Information about parameter a'),
            ('b',
             'Information about parameter b. '
             'Information about parameter b. '
             'Information about parameter b. '
             'Information about parameter b. '
             ),
        ),
        raises=(
            ('AttributeError',
             'The ``Raises`` section is a list of all exceptions that are '
             'relevant to the interface.'),
        ),
        returns="The return value. True for success, False otherwise.",
        desc="The description may span multiple lines. Following lines should "
             'be indented. The type is optional. '
             'The description may span multiple lines. Following lines should '
             'be indented. The type is optional. '
             'The description may span multiple lines. Following lines should '
             'be indented. The type is optional. '
             'The description may span multiple lines. Following lines should '
             'be indented. The type is optional.',
        todo=('Do Something', 'Something else'),
    ).print()


if __name__ == '__main__':
    test()

Выход

The Pear object describes the properties of pears.
Arguments:
    a: Function argument
    b: Function argument

Attributes:
    a: Information about parameter a
    b: Information about parameter b. Information about parameter b. Information
    about parameter b. Information about parameter b.

Raises:
    AttributeError: The ``Raises`` section is a list of all exceptions that are
    relevant to the interface.

Returns:
    The return value. True for success, False otherwise.

Description:
    The description may span multiple lines. Following lines should be indented.
    The type is optional. The description may span multiple lines. Following
    lines should be indented. The type is optional. The description may span
    multiple lines. Following lines should be indented. The type is optional.
    The description may span multiple lines. Following lines should be indented.
    The type is optional.

Todo:
    * Do Something
    * Something else

  • 2

    @Daro, из описания проблемы похоже, что конфигурация строки документации представлена ​​в виде словаря. К счастью, класс данных документации можно инициализировать из словаря следующим образом: Documentation(**config)

    — RootTwo

  • if ("Summary" in config) and (config["Summary"] is not None):
    

    Можно упростить до:

    if config.get("summary") is not None:
    
  • args = ["Args:"] + [f"t{arg}: Function argument" for arg in config["Args"]]
    attr = ["Attributes:"] + [f"t{attr}: Information about parameter {attr}" for attr in config["Attributes"]]
    todo = ["Todo:"] + [f"t{star} {todo}" for todo in config["Todo"]]
    

    Все можно перенести в функцию.

    • Вы всегда добавляете f"{Name}:",
    • Вы всегда повторяете config[Name], а также
    • Вы всегда форматируете значение. Которая может быть представлена ​​как строка формата (не f-строка).
    def format_values(config, name, fmt):
        return [f"{name}:"] + [fmt.format(value) for value in config[name]]
    
    format_values(config, "Args", "t{}: Function argument")
    
  • docstring.append(config["Summary"])
    raises = ["Raises: "] + [f"t {config.get('Raises')}"]
    returns = ["Returns: "] + [f"t {config.get('Returns')}"]
    

    Все можно перенести в функцию.

    • Вы добавляете f"{Name}: " кроме Сводки,
    • Вы всегда добавляете config[Name], а также
    • Вы всегда форматируете значение. (Резюме будет "{}")
    def format_value(config, name, fmt, head=True):
        return ([f"{name}: "] if head else []) + [fmt.format(config[name])]
    
    format_value(config, "Summary", "{}", False)
    
  • value = config.get('Description')
    wrapper=textwrap.TextWrapper(subsequent_indent="t", width = 120)
    description = ["Description: "] + [f"t{wrapper.fill(value)}"]
    docstring += description
    

    Чтобы иметь возможность обрабатывать Описание, мы можем изменить format_value (и, возможно, format_values для согласованности) взять lambda чтобы решить, как отображать значение.

    def format_value(config, name, fmt, head=True):
        return ([f"{name}: "] if head else []) + [fmt(config[name])]
    
    wrapper = textwrap.TextWrapper(subsequent_indent="t", width=120)
    docstring += format_value(config, "Description", lambda value: f"t{wrapper.fill(value)}")
    

Посмотрим, как теперь выглядит код:

import textwrap


def format_value(config, name, fmt, head=True):
    return ([f"{name}: "] if head else []) + [fmt(config[name])]


def format_values(config, name, fmt):
    return [f"{name}:"] + [fmt(value) for value in config[name]]


def gendoc(config):
    docstring = []
    if None is not config.get("Summary"):
        docstring += format_value(config, "Summary", lambda summary: summary, False)
    if None is not config.get("Args"):
        docstring += format_values(config, "Args", lambda arg: f"t{arg}: Function argument")
    if None is not config.get("Attributes"):
        docstring += format_values(config, "Attributes", lambda attr: f"t{arg}: Information about parameter {arg}")
    if None is not config.get("Raises"):
        docstring += format_value(config, "Raises", lambda raises: f"t {raises}")
    if None is not config.get("Returns"):
        docstring += format_value(config, "Returns", lambda returns: f"t {returns}")
    if None is not config.get("Description"):
        wrapper = textwrap.TextWrapper(subsequent_indent="t", width=120)
        docstring += format_value(config, "Description", lambda value: f"t{wrapper.fill(value)}")
    if None is not config.get("Todo"):
        star = "*"
        docstring += format_values(config, "Todo", lambda todo: f"t{star} {todo}")

    docstring = "nn".join(docstring)
    return docstring
if None is not config.get({name}):
    docstring += format_value(config, {name}, ...)

Мы видим, что дублируем указанный выше псевдокод. Давайте проигнорируем fmt а также head аргументы format_value а также format_values.

Мы могли бы изменить код на:

docstring = []
for name, fn in ...:
    if config.get(name) is not None:
        docstring += fn(config, name)

Мы можем изменить format_value а также format_values поэтому мы предоставляем fmt а также head. Затем мы возвращаем другую функцию, которая принимает config а также name позвонить в цикл.

def format_value(fmt, head=True):
    def inner(config, name):
        return ([f"{name}: "] if head else []) + [fmt(config[name])]
    return inner

format_value("{}")(config, "Summary")

Затем мы можем определить словарь для хранения результата первого вызова format_value а также format_values. А затем измените код, чтобы перебрать словарь.

Окончательный код:

import textwrap


def format_value(fmt, head=True):
    def inner(config, name):
        return ([f"{name}: "] if head else []) + [fmt(config[name])]
    return inner


def format_values(fmt):
    def inner(config, name):
        return [f"{name}:"] + [fmt(value) for value in config[name]]
    return inner


wrapper = textwrap.TextWrapper(subsequent_indent="t", width=120)
star = "*"

FORMATS = {
    "Summary": format_value(lambda summary: f"{summary}", False),
    "Args": format_values(lambda arg: f"t{arg}: Function argument"),
    "Attributes": format_values(lambda attr: f"t{arg}: Information about parameter {arg}"),
    "Raises": format_value(lambda raises: f"t {raises}"),
    "Returns": format_value(lambda returns: f"t {returns}"),
    "Description": format_value(lambda value: f"t{wrapper.fill(value)}"),
    "Todo": format_values(lambda todo: f"t{star} {todo}"),
}


def gendoc(config):
    docstring = []
    for name, fn in FORMATS.items():
        if config.get(name) is not None:
            docstring += fn(config, name)
    return "nn".join(docstring)

  • 3

    В качестве альтернативы лямбдам формата вы можете использовать string.format, например, "Args": format_values("t{}: Function argument".format). Я предоставляю читателю решить, что яснее.

    — RootTwo

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *