Как стать автором
Обновить

Комментарии 9

Мне не удалось найти хорошее руководство по argparse для начинающих, поэтому я и решил написать такое руководство сам.

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

Занимаюсь ETL и анализом данных, разработчик. Сначала активно использовали argparse, потом мигрировали на Flask-Script. Сейчас комбинируем его с click.

Преимущества миграции на Flask-Script были:
+ меньше кода при создании команд;
+ возможность создавать вложенные команды, это позволяло разбивать их на подклассы. Удобно, когда команд становится более десятка;
+ нормальная документация;
— пришлось написать небольшую обвязку, чтобы все заработало. Кому-то это может показаться трудной задачей;
— не является стандартной библиотекой;

Я бы порекомендовал приглядеться к click. Это отличная замена argparse.
По опыту использования:
+ очень прост и удобен;
+ минимум кода;
+ куча классных встроенных возможностей: диалоги, группировки и др.
+ хорошая документация;
— не является стандартной библиотекой;

Из тяжеловесных инструментов для создания CLI-приложения порекомендовал бы взглянуть на Cement Framework. Сам не пользовался, но отзывы от знакомых были положительные.
Тоже очень нравится click. Особенно приятно работать с путями. Сразу при указании типа аргумента можно провести его валидацию, например:
import click


@click.command()
@click.option(
    "--count",
    default=1,
    type=click.IntRange(min=1, max=100),
    help="Number of files to generate.",
)
@click.option(
    "--output",
    type=click.Path(exists=True, file_okay=False, writable=True),
    help="Output directory script will write files to.",
)
def file_generator(count, output):
    for i in range(count):
        with open(f"{i}.txt", "w") as f:
            f.write(f"Dummy data {i}")


if __name__ == "__main__":
    file_generator()


Вот пользуюсь и не могу нарадоваться, от скольких же рутинных проверок этот инструмент меня избавил.

P. S. Да, во время генерации самих файлов проверки все еще нужны, но это уже другая история. :)

P. P. S. Форматировался код при помощи black
Я так и не разобрался, как в click сделать поддержку списков аргументов, например `--my-args first second third`.

Можете подсказать?

Судя по документации, можно сделать либо с ограниченным количеством аргументов (в данном случае — 3 аргумента; определит из длины кортежа type):


import click

@click.command()
@click.option('--userdata', type=(str, str, int), help='User data represented as "FirstName LastName Age"')
def greet(userdata):
    print('Hello, user {} {}. You are {} y.o.'.format(*userdata))

if __name__ == '__main__':
    greet()

Что можно использовать как:
python app.py --userdata Vasiliy Petrenko 42


Если нужно передавать несколько одинаковых параметров — можно делать так:


import click

@click.command()
@click.option('--name', type=str, help='A name to greet', multiple=True)
def greet(name):
    for n in name:
        print(f'Hello, {n}')

if __name__ == '__main__':
    greet()

И использовать как:
python app.py --name Vasya --name Petya


Указывать неограниченное количество аргументов для Option (например, python app.py --names Vasya Petya Tolya) — нельзя.

Спасибо. Меня это напрягло, учитывая что argparse это умеет.

А без костылей способа, чтобы было как в argparse и красиво, не находил.


1) В дополнение к вариантам выше:


import click

@click.command()
@click.argument('numbers', nargs=-1)
@click.option('--word')
def fancy_command(numbers, word):
    result = sum(int(s) for s in numbers)
    print('Numbers sum: {}. Word: {}'.format(result, word))

if __name__ == '__main__':
    fancy_command()

Получим:


$ python script.py 1 2 3 4 5 --word Hello
Numbers sum: 15. Word: Hello

2) Ну и костыльный способ:


import click

@click.command()
@click.option('--numbers', nargs=0)
@click.argument('numbers', nargs=-1)
def fancy_command(numbers):
    result = sum(int(s) for s in numbers)
    print('Numbers sum: {}'.format(result))

if __name__ == '__main__':
    fancy_command()

Получим:


$ python script.py --numbers 1 2 3
Numbers sum: 6
1) Как-то не раскрыто, что `type` может вам помогать в качестве валидатора, кроме как для приведения значения в тот вид что вам нужен

import os
import argparse

def validate_path(path: str) -> str:
    if not os.path.isabs(path):
        raise argparse.ArgumentTypeError(f'Absolute path required, got "{path}"')

    return os.path.normpath(path)

parser.add_argument('--path', metavar='PATH', type=validate_path)


2) Если хотите чтобы параметры по умолчанию отображались в помощи, можно использовать такой ArgumentParser

import argparse
import typing


class RawTextArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
    'Adds default arguments to parser help output'
    def _get_help_string(self, action: argparse.Action) -> str:
        help_str = action.help or ''
        if 'default: ' in help_str or action.default is argparse.SUPPRESS or action.default is None or action.default is False:
            return help_str

        if not action.option_strings and action.nargs not in [argparse.OPTIONAL, argparse.ZERO_OR_MORE]:
            return help_str

        return f'{help_str} (default: {action.default})'


class ArgumentParser(argparse.ArgumentParser):
    '`argparse.ArgumentParser` that shows default values in help message.'
    def __init__(self, **kwargs: typing.Any) -> None:
        kwargs['formatter_class'] = RawTextArgumentDefaultsHelpFormatter
        super(ArgumentParser, self).__init__(**kwargs)

Питон для самых маленьких?
Документация и читается и пробуется за 30 минут. Зачем нужны такие "гайды"-обрезки — не понятно. К тому же и они уже были:
https://m.habr.com/ru/post/144416/
https://jenyay.net/Programming/Argparse
https://rtfm.co.ua/python-modul-argparse-opcii-komandnoj-stroki-v-primerax/
Впрочем что я требую от ruvds? У них же никогда не было годных статей...

Зарегистрируйтесь на Хабре , чтобы оставить комментарий