Python argparse: Введіть хоча б один аргумент


92

Я використовував 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примусовий вибір принаймні одного параметра?

ОНОВЛЕННЯ:

Слідом за коментарями: Який пітонічний спосіб параметризувати програму хоча б з одним варіантом?


9
-xє універсальним прапором та необов’язковим. Виріжте, -якщо це потрібно.

1
Не могли б ви зробити processповедінку за замовчуванням (без необхідності вказувати будь-які параметри) і дозволити користувачеві змінити це на, uploadякщо ця опція встановлена? Зазвичай варіанти повинні бути необов’язковими, звідси і назва. Слід уникати необхідних варіантів (це також є в argparse документації).
Тім Пітцкер,

@AdamMatan Минуло майже три роки з того часу, як ти задав своє запитання, але мені сподобався прихований у ньому виклик і я використав перевагу нових рішень, доступних для такого роду завдань.
Ян Влчинський

Відповіді:


107
if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')

1
Це, мабуть, єдиний спосіб, якщо для argparseцього немає вбудованої опції.
Адам Матан,

29
args = vars(parser.parse_args())
if not any(args.values()):
    parser.error('No arguments provided.')

3
+1 для узагальненого рішення. Також як використання vars(), що також корисно для передачі ретельно названих параметрів конструктору з **.
Lenna

Що саме я з цим роблю. Дякую!
brentlance

1
Данг, мені це подобається vars. Я просто так .__dict__і почувався німим раніше.
Тео Белер

1
чудові відповіді. І "vars", і "any" були для мене новими :-)
Vivek Jha

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
Я думаю, що він хоче дозволити --processАБО --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  

3
На жаль, операційна система не хоче XOR. Це або одне, і інше, але не жодне, тому ваш останній тест не відповідає їхнім вимогам.
kdopen

2
@kdopen: респондент дійсно пояснив, що це варіація вихідного питання, яке я визнав корисним: "спосіб вимагати одного варіанту, але забороняти більше одного" Можливо, етикет Stack Exchange вимагатиме замість цього нового питання . Але наявність цієї відповіді тут мені допомогло ...
erik.weathers

2
Цей пост не відповідає на початкове запитання
Марк,

2
Як це відповідає на запитання "хоча б одного"?
xaxxon

2
На жаль, операційна система не хоче XOR.
duckman_1991

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


Ніколи не чув про докопт, чудова пропозиція!
Тон ван ден Хойвел,

@TonvandenHeuvel Добре. Я просто хочу підтвердити, я все ще використовую його як своє найкраще рішення для інтерфейсів командного рядка.
Ян Влчинський,

Краща відповідь evar, дякую за докладні приклади.
jnovack

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)зрозуміле використання ? Потрібна взаємно_виключна_група виробляє те саме. Чи є якісь альтернативні позначення?

  • використовує подібну групу більш інтуїтивно, ніж phihag'sпростий тест?


Я не можу знайти жодної згадки 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
На жаль, операційна система не хоче XOR. ОП шукає АБО
duckman_1991

Це не дало відповіді на запитання 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 , що є тим, чого хочуть більшість розумних програмістів (також працює з необов’язковими аргументами):

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.