Python argparse: сделайте хотя бы один аргумент обязательным

Я использовал 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 параметризовать программу хотя бы с одним параметром?


person Adam Matan    schedule 17.07.2011    source источник
comment
-x является флагом и необязательным. Отрежьте -, если это необходимо.   -  person    schedule 17.07.2011
comment
Не могли бы вы сделать process поведением по умолчанию (без необходимости указывать какие-либо параметры) и позволить пользователю изменить его на upload, если установлен эта опция? Обычно параметры должны быть необязательными, отсюда и название. Следует избегать обязательных опций (это также есть в argparse документах).   -  person Tim Pietzcker    schedule 17.07.2011
comment
@AdamMatan Прошло почти три года с тех пор, как вы задали свой вопрос, но мне понравилась скрытая в нем проблема, и я использовал преимущество новых решений, доступных для такого рода задач.   -  person Jan Vlcinsky    schedule 10.06.2014


Ответы (11)


Я знаю, что это старо как грязь, но способ потребовать одну опцию, но запретить более одной (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  
person Knut    schedule 09.10.2015
comment
К сожалению, OP не хочет XOR. Это либо то, либо другое, но не ни одно, поэтому ваш последний тестовый пример не соответствует их требованиям. - person kdopen; 10.10.2015
comment
@kdopen: респондент пояснил, что это вариант исходного вопроса, который я нашел полезным: способ потребовать один вариант, но запретить более одного. Возможно, этикет Stack Exchange потребует вместо этого новый вопрос. Но наличие этого ответа здесь помогло мне... - person erik.weathers; 20.12.2015
comment
Я поддержу полезность этого ответа, это оказалось именно то, что я искал. - person Mark Edington; 08.11.2017
comment
Этот пост не отвечает на первоначальный вопрос - person Marc; 16.01.2018
comment
По крайней мере, это было именно то, что я искал, так что большое спасибо. - person T.Nel; 02.07.2018
comment
Как это отвечает на вопрос хотя бы об одном? - person xaxxon; 22.07.2019
comment
К сожалению, OP не хочет XOR. - person iamthedrake; 01.10.2019

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

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")

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

person Jacek Konieczny    schedule 17.07.2011
comment
Я думаю, он хочет разрешить --process ИЛИ --upload, а не XOR. Это предотвращает одновременное задание обоих параметров. - person phihag; 17.07.2011
comment
+1, потому что вы упомянули подкоманды. Тем не менее, как кто-то указал в комментариях, -x и --xxx обычно являются необязательными параметрами. - person mac; 17.07.2011

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

  • используйте 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 подходящим для синтаксического анализа командной строки.

person Jan Vlcinsky    schedule 09.06.2014
comment
Никогда не слышал о docopt, отличное предложение! - person Ton van den Heuvel; 18.02.2015
comment
@TonvandenHeuvel Хорошо. Я просто хочу подтвердить, что я все еще использую его как предпочтительное решение для интерфейсов командной строки. - person Jan Vlcinsky; 19.02.2015
comment
Лучший ответ эвар, спасибо за подробные примеры. - person jnovack; 05.02.2016

Для 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)? Требуемая взаимоисключающая_группа производит то же самое. Есть ли какие-то альтернативные обозначения?

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

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

Если вам требуется, чтобы программа Python запускалась хотя бы с одним параметром, добавьте аргумент, который не имеет префикс option (- или -- по умолчанию) и установите 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="+", он ожидает как минимум один аргумент в целом.)

person NuclearPeon    schedule 20.07.2013
comment
Вы можете добавить choices=['process','upload'] к этому аргументу. - person hpaulj; 10.06.2014

Лучший способ сделать это — использовать встроенный модуль 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)
person faizan baig    schedule 10.06.2017
comment
Как это даст вам хотя бы один - разве это не даст вам ровно один? - person xaxxon; 22.07.2019
comment
К сожалению, OP не хочет XOR. ОП ищет ИЛИ - person iamthedrake; 01.10.2019
comment
Это не ответило на вопрос ОП, но ответило на мой, так что все равно спасибо ¯_(ツ)_/¯ - person rosstex; 25.04.2020

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

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' вы также можете связать функции, которые будут напрямую вызываться данной подкомандой (см. документы).

person jhutar    schedule 09.04.2019

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

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

person Bob    schedule 13.07.2019

Используйте 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()
person storm_m2138    schedule 10.06.2014

person    schedule
comment
Это, наверное, единственный способ, если argparse не имеет для этого встроенной опции. - person Adam Matan; 17.07.2011

person    schedule
comment
+1 за обобщенное решение. Также нравится использование vars(), которое также полезно для передачи тщательно названных опций конструктору с помощью **. - person Lenna; 10.03.2013
comment
Что я и делаю. Спасибо! - person brentlance; 11.03.2013
comment
Черт, мне это нравится vars. Я только что сделал .__dict__ и раньше чувствовал себя глупо. - person Theo Belaire; 13.02.2014
comment
отличные ответы. И vars, и any были для меня в новинку :-) - person Vivek Jha; 25.01.2016