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

argparse не справляется с подкомандами, получающими глобальные параметры:

import argparse
p = argparse.ArgumentParser()
p.add_argument('--arg', action='store_true')
s = p.add_subparsers()
s.add_parser('test')

будет работать p.parse_args('--arg test'.split()),
но не работает p.parse_args('test --arg'.split()).

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


person Ronny    schedule 05.06.2012    source источник
comment
Что вы имеете в виду под «не удается»? Что вы хотите, чтобы произошло? Какие глобальные варианты?   -  person alan    schedule 05.06.2012
comment
терпит неудачу, как в жалобе на неизвестный аргумент --arg   -  person Ronny    schedule 05.06.2012


Ответы (5)


Попробуйте docopt:

>>> from docopt import docopt

>>> usage = """
... usage: prog.py command [--test]
...        prog.py another [--test]
... 
... --test  Perform the test."""

>>> docopt(usage, argv='command --test')
{'--test': True,
 'another': False,
 'command': True}

>>> docopt(usage, argv='--test command')
{'--test': True,
 'another': False,
 'command': True}
person Vladimir Keleshev    schedule 05.06.2012
comment
@Руни, отлично! и спасибо за ваш отзыв, больше этого всегда приветствуется. - person Vladimir Keleshev; 05.06.2012

Вы можете легко добавить этот аргумент в оба парсера (главный парсер и парсер подкоманд):

import argparse                                                                  

main = argparse.ArgumentParser()                                                    
subparser = main.add_subparsers().add_parser('test')                                        

for p in [main,subparser]:                                                                  
   p.add_argument('--arg', action='store_true')                                 

print main.parse_args('--arg test'.split()).arg                                     
print main.parse_args('test --arg'.split()).arg

Изменить: как указал @hpaulj в комментарии, есть также аргумент parents, который вы можете передать конструктору ArgumentParser или методу add_parser. Вы можете перечислить в этом значении парсеры, которые являются базой для нового.

import argparse

base = argparse.ArgumentParser(add_help=False)
base.add_argument('--arg', action='store_true')

main = argparse.ArgumentParser(parents=[base])
subparser = main.add_subparsers().add_parser('test', parents=[base])

print main.parse_args('--arg test'.split()).arg
print main.parse_args('test --arg'.split()).arg

Дополнительные примеры/документы:

поиск лучшего способа предоставления аргументов командной строки в python, где некоторые параметры требуются для некоторых параметров, а некоторые параметры требуются для других параметров

Python argparse — добавить аргумент в несколько подпарсеров (я не уверен если этот вопрос не слишком сильно пересекается с этим)

http://docs.python.org/dev/library/argparse.html#parents

person paluh    schedule 29.08.2013
comment
Если бы опций было больше, их можно было бы определить в синтаксическом анализаторе parent и добавить как к p, так и к s через параметр parents. На stackoverflow.com/a/18346152/901925 есть пример добавления одного и того же набора опций ко многим подпарсерам. - person hpaulj; 30.08.2013
comment
@hpaulj - я думаю, что это еще лучший ответ! Я собираюсь обновить свой ответ... - person paluh; 03.09.2013
comment
В Python 2.7.16 второй пример печатает False в первом случае, что, я считаю, совсем не желаемое поведение? Поэтому я считаю второе, отредактированное решение неправильным. - person Dale; 19.07.2019

В мире Python существует множество библиотек для разбора аргументов. Вот несколько, которые я видел, и все они должны быть в состоянии решить проблему, которую вы пытаетесь решить (на основе моих смутных воспоминаний о них, когда я играл с ними в последний раз):

  • opster — я думаю, это то, что использует Mercurial, IIRC
  • docopt — это новый документ, но в нем используется интересный подход.
  • cliff — это относительно новый проект Дуга Хеллманна (член PSF, автор virtualenvwrapper, общий экстраординарный хакер) - это немного больше, чем просто анализатор аргументов, он разработан с нуля для обработки многоуровневых команд.
  • clint – еще один проект, целью которого является "разбор аргументов и многое другое", на этот раз Кеннет Рейц. (Запросы славы).
person Hank Gay    schedule 05.06.2012
comment
Вау, я не знал, что есть так много вариантов. Думаю, это потому, что мне всегда удавалось уговорить argparse подчиниться. +1 за то, что нашел все это... - person mgilson; 05.06.2012
comment
я провел быстрое расследование, докопт и клинт не будут делать обрыв, это очень странная и сложная вещь, я не хочу понимать, опстер выглядит интересно, попробую - person Ronny; 05.06.2012
comment
@Ronny, docopt должен работать - вы можете добавить ярлык [options], он позволяет передавать глобальные параметры независимо от иерархии подкоманд. - person Vladimir Keleshev; 05.06.2012
comment
@Ronny, то есть случай 'test --arg' будет распознан, если вы укажете шаблон my_program test [options] или my_program test [--arg]. - person Vladimir Keleshev; 05.06.2012

Вот грязный обходной путь -

import argparse
p = argparse.ArgumentParser()
p.add_argument('--arg', action='store_true')
s = p.add_subparsers()
s.add_parser('test')

def my_parse_args(ss):
    #parse the info the subparser knows about; don't issue an error on unknown stuff
    namespace,leftover=p.parse_known_args(ss) 
    #reparse the unknown as global options and add it to the namespace.
    if(leftover):
        s.add_parser('null',add_help=False)
        p.parse_args(leftover+['null'],namespace=namespace)

    return namespace

#print my_parse_args('-h'.split())  #This works too, but causes the script to stop.
print my_parse_args('--arg test'.split())
print my_parse_args('test --arg'.split())

Это работает -- и вы можете довольно легко изменить его для работы с sys.argv (просто удалите разделяемую строку "ss"). Вы даже можете создать подкласс argparse.ArgumentParser и заменить метод parse_args на my_parse_args, и тогда вы никогда не заметите разницы -- хотя создание подкласса для замены одного метода кажется мне излишним.

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

person mgilson    schedule 05.06.2012

Парсер имеет особый синтаксис: command <global options> subcommand <subcommand ptions>, вы пытаетесь передать подкоманде опцию, но не определили ее.

person KurzedMetal    schedule 05.06.2012
comment
Я думаю, что Ронни знает, почему это не удается (или, по крайней мере, ему все равно) - он ищет обходной путь (используя argparse или что-то еще). - person mgilson; 05.06.2012