Python argparse: укажите хотя бы один аргумент

96

Я использовал argparseпрограмму Python, которая может или и то -process, -uploadи другое:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload',  action='store_true')
args = parser.parse_args()

Программа бессмысленна без хотя бы одного параметра. Как я могу настроить argparseпринудительный выбор хотя бы одного параметра?

ОБНОВИТЬ:

Следуя комментариям: Каким способом Pythonic параметризовать программу хотя бы с одной опцией?

Адам Матан
источник
9
-xвсегда является флагом и необязателен. Отрежьте, -если это необходимо.
1
Не могли бы вы сделать processповедение по умолчанию (без необходимости указывать какие-либо параметры) и разрешить пользователю изменить его, uploadесли этот параметр установлен? Обычно параметры должны быть необязательными, отсюда и название. Обязательных параметров следует избегать (это также есть в argparse документации).
Тим Пицкер
@AdamMatan Прошло почти три года с тех пор, как вы задали свой вопрос, но мне понравилась проблема, скрытая в нем, и я воспользовался преимуществом новых решений, доступных для такого рода задач.
Jan Vlcinsky

Ответы:

113
if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')
Phihag
источник
1
Это, наверное, единственный способ, если для argparseэтого нет встроенной опции.
Adam Matan
32
args = vars(parser.parse_args())
if not any(args.values()):
    parser.error('No arguments provided.')
Brentlance
источник
3
+1 для обобщенного решения. Также как использование vars(), которое также полезно для передачи тщательно названных параметров конструктору с **.
Lenna
Именно это я и делаю. Благодарность!
brentlance
1
Черт, мне это нравится vars. Я просто так делал .__dict__и раньше чувствовал себя немым.
Theo Belaire
1
отличные ответы. И "vars", и "any" были для меня новыми :-)
Вивек Джа
21

Если не часть «или обе» (я сначала пропустил это), вы можете использовать что-то вроде этого:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('--process', action='store_const', const='process', dest='mode')
parser.add_argument('--upload',  action='store_const', const='upload', dest='mode')
args = parser.parse_args()
if not args.mode:
    parser.error("One of --process or --upload must be given")

Хотя, вероятно, было бы лучше использовать вместо этого подкоманды .

Яцек Конечны
источник
4
Я думаю, он хочет разрешить --processOR --upload, а не XOR. Это предотвращает одновременную установку обоих параметров.
phihag
+1, потому что вы упомянули подкоманды. Тем не менее - как кто-то указал в комментариях -xи --xxxобычно являются необязательными параметрами.
mac
20

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

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()
print args

Выход:

>opt.py  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: one of the arguments -process -upload is required  

>opt.py -upload  
Namespace(process=False, upload=True)  

>opt.py -process  
Namespace(process=True, upload=False)  

>opt.py -upload -process  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: argument -process: not allowed with argument -upload  
Кнут
источник
4
К сожалению, OP не хочет XOR. Один или оба варианта, но не ни один, поэтому ваш последний тестовый пример не соответствует их требованиям.
kdopen
3
@kdopen: респондент пояснил, что это вариант исходного вопроса, который я нашел полезным: «способ потребовать один вариант, но запретить более одного». Возможно, этикет Stack Exchange потребует вместо этого нового вопроса . Но наличие этого ответа здесь помогло мне ...
erik.weathers
2
Этот пост не отвечает на первоначальный вопрос
Марк
2
Как это отвечает на вопрос «хотя бы один»?
xaxxon
2
К сожалению, OP не хочет XOR.
duckman_1991 01
8

Обзор требований

  • использовать argparse(я проигнорирую это)
  • разрешить вызов одного или двух действий (требуется хотя бы одно).
  • попробуйте с помощью Pythonic (я бы назвал это "POSIX" -подобным)

При работе в командной строке также существуют некоторые неявные требования:

  • объясните пользователю использование в понятной форме
  • опции должны быть необязательными
  • разрешить указывать флаги и параметры
  • разрешить комбинирование с другими параметрами (такими как имя файла или имена).

Пример решения с использованием docopt(файл managelog.py):

"""Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Попробуйте запустить его:

$ python managelog.py
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Показать справку:

$ python managelog.py -h
Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  P    managelog.py [options] upload -- <logfile>...

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>

И используйте это:

$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
{'--': True,
 '--pswd': 'secret',
 '--user': 'user',
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': False,
 'upload': True}

Краткая альтернатива short.py

Возможен и более короткий вариант:

"""Manage logfiles
Usage:
    short.py [options] (process|upload)... -- <logfile>...
    short.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Использование выглядит так:

$ python short.py -V process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 1,
 'upload': 1}

Обратите внимание, что вместо логических значений для ключей "процесс" и "загрузка" используются счетчики.

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

$ python short.py -V process process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 2,
 'upload': 1}

Выводы

Иногда создание хорошего интерфейса командной строки может быть сложной задачей.

Программа на основе командной строки имеет несколько аспектов:

  • хороший дизайн командной строки
  • выбор / использование подходящего парсера

argparse предлагает много, но ограничивает возможные сценарии и может стать очень сложным.

Благодаря этому docoptвсе становится намного короче, сохраняя при этом удобочитаемость и обеспечивая высокую степень гибкости. Если вам удастся получить проанализированные аргументы из словаря и выполнить некоторые преобразования (в целые числа, открытие файлов ...) вручную (или с помощью другой вызываемой библиотеки schema), вы можете найти docoptподходящий вариант для синтаксического анализа командной строки.

Ян Влчинский
источник
Никогда не слышал о docopt, отличное предложение!
Тон ван ден Хеувель
@TonvandenHeuvel Хорошо. Я просто хочу подтвердить, я все еще использую его в качестве предпочтительного решения для интерфейсов командной строки.
Ян Влчинский,
Лучший ответ evar, спасибо за подробные примеры.
jnovack 05
5

Если вам требуется, чтобы программа Python запускалась хотя бы с одним параметром, добавьте аргумент, который не имеет префикса параметра (- или - по умолчанию), и установите nargs=+(Требуется минимум один аргумент). Проблема с этим методом, который я обнаружил, заключается в том, что если вы не укажете аргумент, argparse выдаст ошибку «слишком мало аргументов» и не распечатает меню справки. Если вам не нужны эти функции, вот как это сделать в коде:

import argparse

parser = argparse.ArgumentParser(description='Your program description')
parser.add_argument('command', nargs="+", help='describe what a command is')
args = parser.parse_args()

Я думаю, что когда вы добавляете аргумент с префиксами опций, nargs управляет всем парсером аргументов, а не только опцией. (То , что я имею в виду, если у вас есть --optionфлаг с nargs="+", то --optionфлаг ожидает , по крайней мере один аргумент. Если у вас есть optionс nargs="+", он ожидает , что по крайней мере один аргумент в целом.)

ЯдерныйПеон
источник
Вы можете добавить choices=['process','upload']к этому аргументу.
hpaulj
5

Для http://bugs.python.org/issue11588 я изучаю способы обобщения mutually_exclusive_groupконцепции для обработки подобных случаев.

С этим развитием argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py я могу написать:

parser = argparse.ArgumentParser(prog='PROG', 
    description='Log archiver arguments.')
group = parser.add_usage_group(kind='any', required=True,
    title='possible actions (at least one is required)')
group.add_argument('-p', '--process', action='store_true')
group.add_argument('-u', '--upload',  action='store_true')
args = parser.parse_args()
print(args)

что дает следующее help:

usage: PROG [-h] (-p | -u)

Log archiver arguments.

optional arguments:
  -h, --help     show this help message and exit

possible actions (at least one is required):
  -p, --process
  -u, --upload

Он принимает такие входы, как '-u', '-up', '--proc --up' и т. Д.

В итоге он запускает тест, аналогичный https://stackoverflow.com/a/6723066/901925 , хотя сообщение об ошибке должно быть более четким:

usage: PROG [-h] (-p | -u)
PROG: error: some of the arguments process upload is required

Я думаю:

  • kind='any', required=TrueДостаточно ли ясны параметры (принять любую из группы; требуется хотя бы один)?

  • (-p | -u)понятно ли использование ? Требуемая группа mutually_exclusive_group производит то же самое. Есть ли альтернативные обозначения?

  • использование такой группы более интуитивно, чем phihag'sпростой тест?

hpaulj
источник
Я не могу найти упоминания add_usage_groupна этой странице: docs.python.org/2/library/argparse.html ; не могли бы вы дать ссылку на его документацию?
П. Майер Нор
@ P.MyerNore, я дал ссылку - в начале этого ответа. Это еще не запущено в производство.
hpaulj
5

Лучший способ сделать это - использовать встроенный в Python модуль add_mutually_exclusive_group .

parser = argparse.ArgumentParser(description='Log archiver arguments.')
group = parser.add_mutually_exclusive_group()
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()

Если вы хотите, чтобы в командной строке был выбран только один аргумент, просто используйте required = True в качестве аргумента для группы

group = parser.add_mutually_exclusive_group(required=True)
Файзан Бэйг
источник
2
Как это дает вам «хотя бы один» - разве это не дает вам «ровно один»?
xaxxon
3
К сожалению, OP не хочет XOR. ОП ищет
операционную
Это не ответило на вопрос OP, но оно ответило на мой, так что все равно спасибо ¯_ (ツ) _ / ¯
rosstex
2

Может, использовать суб-парсеры?

import argparse

parser = argparse.ArgumentParser(description='Log archiver arguments.')
subparsers = parser.add_subparsers(dest='subparser_name', help='sub-command help')
parser_process = subparsers.add_parser('process', help='Process logs')
parser_upload = subparsers.add_parser('upload', help='Upload logs')
args = parser.parse_args()

print("Subparser: ", args.subparser_name)

Теперь --helpпоказывает:

$ python /tmp/aaa.py --help
usage: aaa.py [-h] {process,upload} ...

Log archiver arguments.

positional arguments:
  {process,upload}  sub-command help
    process         Process logs
    upload          Upload logs

optional arguments:
  -h, --help        show this help message and exit
$ python /tmp/aaa.py
usage: aaa.py [-h] {process,upload} ...
aaa.py: error: too few arguments
$ python3 /tmp/aaa.py upload
Subparser:  upload

Вы также можете добавить дополнительные параметры в эти суб-парсеры. Кроме того, вместо этого dest='subparser_name'вы также можете привязать функции для прямого вызова данной подкоманды (см. Документацию).

джутар
источник
2

Это достигает цели, и это также будет отражено в автоматически сгенерированном --helpвыводе argparse , что imho то, что хотят большинство здравомыслящих программистов (также работает с необязательными аргументами):

parser.add_argument(
    'commands',
    nargs='+',                      # require at least 1
    choices=['process', 'upload'],  # restrict the choice
    help='commands to execute'
)

Официальные документы по этому поводу: https://docs.python.org/3/library/argparse.html#choices

Боб
источник
1

Используйте append_const в список действий, а затем убедитесь, что список заполнен:

parser.add_argument('-process', dest=actions, const="process", action='append_const')
parser.add_argument('-upload',  dest=actions, const="upload", action='append_const')

args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

Вы даже можете указать методы прямо в константах.

def upload:
    ...

parser.add_argument('-upload',  dest=actions, const=upload, action='append_const')
args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

else:
    for action in args.actions:
        action()
storm_m2138
источник