Как указать значение по умолчанию для списка аргументов, обрабатываемого по клику?

У меня есть эта строка кода, которая, как ожидается, захватит все имена файлов, переданные моему скрипту Python:

@click.argument("logs", nargs=-1, type=click.File('r'), required=1)

Когда имена файлов не передаются, я хочу по умолчанию использовать -, то есть стандартный ввод. Итак, если я попытаюсь:

@click.argument("logs", nargs=-1, type=click.File('r'), required=1, default = "-")

click становится несчастным и выдает эту ошибку:

TypeError: nargs=-1 in combination with a default value is not supported.

Есть ли обходной путь для этого? Я попытался установить nargs=0, но выдает другую ошибку:

IndexError: tuple index out of range

Что бы это ни стоило, соответствующий проблема на гитхабе (т.е. они не исправили это, они явно запретили это).

metatoaster 29.05.2019 02:25
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
1
5 886
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Следующий код:

import click


@click.command()
@click.option('--logs', '-l', multiple=True, default='-')
def cli(logs):
    click.echo('\n'.join(logs))

Произведет следующее:

$ myhello -l foo.log -l bar.log -l baz.log
foo.log
bar.log
baz.log
$ myhello
-

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

Это меняет семантику того, что хотел OP, так что да, это будет просто обходной путь, который не решает реальную проблему.

metatoaster 29.05.2019 02:23

в этом случае я бы просто добавил простую обертку вокруг вышеперечисленного в bash.

Jimmy Engelbrecht 29.05.2019 02:49
Ответ принят как подходящий

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

Обходной путь — просто удалить требуемый аргумент и обработать отсутствующие журналы с помощью оператора (аналогично тому, что сделал этот коммит, который ссылался на связанную проблему):

import sys
import click

@click.command()
@click.argument("logs", nargs=-1, type=click.File('r'))
def main(logs):
    if not logs:
        logs = (sys.stdin,)
    print(logs)
    # do stuff with logs

if __name__ == '__main__': 
    main()

Пример исполнения

$ python fail.py 
(<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>,)
$ python fail.py -
(<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>,)
$ python fail.py foo bar
(<_io.TextIOWrapper name='foo' mode='r' encoding='UTF-8'>, <_io.TextIOWrapper name='bar' mode='r' encoding='UTF-8'>)

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

Пользовательский класс:

class FilesDefaultToStdin(click.Argument):
    def __init__(self, *args, **kwargs):
        kwargs['nargs'] = -1
        kwargs['type'] = click.File('r')
        super().__init__(*args, **kwargs)

    def full_process_value(self, ctx, value):
        return super().process_value(ctx, value or ('-', ))

Определение этого поведения как класса обеспечивает простоту повторного использования.

Чтобы использовать пользовательский класс:

@click.command()
@click.argument("logs", cls=FilesDefaultToStdin)
def main(logs):
    ...

Как это работает?

Это работает, потому что click — это хорошо спроектированная объектно-ориентированная среда. Декоратор @click.argument() обычно создает экземпляр объекта click.Argument, но позволяет переопределить это поведение с помощью параметра cls. Таким образом, относительно легко наследоваться от click.Argument в нашем собственном классе и переопределять нужные методы.

В этом случае мы переопределяем click.Argument.full_process_value(). В нашем full_process_value() мы ищем пустой список аргументов, и если он пуст, мы добавляем в список аргумент - (stdin).

Кроме того, мы автоматически присваиваем аргументы nargs=-1 и type=click.File('r').

Другие вопросы по теме