2011-12-15 2 views
19

Я следующий код (с помощью Python 2.7):argparse опционально subparser (для --version)

# shared command line options, like --version or --verbose 
parser_shared = argparse.ArgumentParser(add_help=False) 
parser_shared.add_argument('--version', action='store_true') 

# the main parser, inherits from `parser_shared` 
parser = argparse.ArgumentParser(description='main', parents=[parser_shared]) 

# several subcommands, which can't inherit from the main parser, since 
# it would expect subcommands ad infinitum 
subparsers = parser.add_subparsers('db', parents=[parser_shared]) 

... 

args = parser.parse_args() 

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

$ prog --version 
0.1 

$ prog db --version 
0.1 

В принципе, мне нужно объявить дополнительные subparsers. Я знаю, что это не really supported, но есть ли какие-либо обходные пути или альтернативы?

Edit: Сообщение об ошибке я получаю:

$ prog db --version 
# works fine 

$ prog --version 
usage: .... 
prog: error: too few arguments 

ответ

17

Согласно документации, --version с action='version' (а не с action='store_true') печатает автоматически номер версии:

parser.add_argument('--version', action='version', version='%(prog)s 2.0') 
2

Да, я просто checked svn, который используется как пример объекта в add_subparsers() documentation, и он поддерживает только «-версию» по основной команде:

python zacharyyoung$ svn log --version 
Subcommand 'log' doesn't accept option '--version' 
Type 'svn help log' for usage. 

Еще:

# create common parser 
parent_parser = argparse.ArgumentParser('parent', add_help=False) 
parent_parser.add_argument('--version', action='version', version='%(prog)s 2.0') 

# create the top-level parser 
parser = argparse.ArgumentParser(parents=[parent_parser]) 
subparsers = parser.add_subparsers() 

# create the parser for the "foo" command 
parser_foo = subparsers.add_parser('foo', parents=[parent_parser]) 

Что дает:

python zacharyyoung$ ./arg-test.py --version 
arg-test.py 2.0 
python zacharyyoung$ ./arg-test.py foo --version 
arg-test.py foo 2.0 
1

В то время как мы wait for this feature быть доставлены, мы можем использовать такой код:

# Make sure that main is the default sub-parser 
if '-h' not in sys.argv and '--help' not in sys.argv: 
    if len(sys.argv) < 2: 
     sys.argv.append('main') 
    if sys.argv[1] not in ('main', 'test'): 
     sys.argv = [sys.argv[0], 'main'] + sys.argv[1:] 
+0

Обратите внимание, что мы ждем этой основной функции с 2009 года. – yac

+0

Я начал использовать docopt вместо встроенного анализатора аргументов. Он поддерживает смешанное использование с или без «действий», иначе называемых «глаголами». http://docopt.org/ –

7

FWIW, я столкнулся это также, и в конечном итоге «решает» его, не используя подпараметров (у меня уже был свой система для печати справки, поэтому ничего там не теряла).

Вместо этого я делаю это:

parser.add_argument("command", nargs="?", 
        help="name of command to execute") 

args, subcommand_args = parser.parse_known_args() 

... а затем субкоманда создает свой собственный парсер (по аналогии с subparser), который работает только на subcommand_args.

3

Как обсуждалось в http://bugs.python.org/issue9253 (argparse: необязательные подпараметры), с Python 3.3 подпаранты теперь являются необязательными. Это был непреднамеренный результат изменения того, как parse_args проверял необходимые аргументы.

Я нашел fudge, который восстанавливает предыдущие (требуемые подпарамеры) поведение, явно устанавливая атрибут required действия subparsers.

parser = ArgumentParser(prog='test') 
subparsers = parser.add_subparsers() 
subparsers.required = True # the fudge 
subparsers.dest = 'command' 
subparser = subparsers.add_parser("foo", help="run foo") 
parser.parse_args() 

См. Эту проблему для получения более подробной информации. Я ожидаю, что если и когда эта проблема будет исправлена, по умолчанию потребуются подпарамеры, с некоторой опцией установить свой атрибут required на False. Но есть большое отставание от argparse патчей.

0

Хотя адрес ответа @ eumiro --version, он может это сделать только потому, что это особый случай для optparse.Чтобы разрешить общие инвокации:

prog 
prog --verbose 
prog --verbose main 
prog --verbose db 

и имеют prog --version работу так же, как prog --verbose mainprog main --verbose), вы можете добавить метод Argumentparser и назвать, что с именем subparser по умолчанию, как раз перед вызовом parse_args():

import argparse 
import sys 

def set_default_subparser(self, name, args=None): 
    """default subparser selection. Call after setup, just before parse_args() 
    name: is the name of the subparser to call by default 
    args: if set is the argument list handed to parse_args() 

    , tested with 2.7, 3.2, 3.3, 3.4 
    it works with 2.6 assuming argparse is installed 
    """ 
    subparser_found = False 
    for arg in sys.argv[1:]: 
     if arg in ['-h', '--help']: # global help if no subparser 
      break 
    else: 
     for x in self._subparsers._actions: 
      if not isinstance(x, argparse._SubParsersAction): 
       continue 
      for sp_name in x._name_parser_map.keys(): 
       if sp_name in sys.argv[1:]: 
        subparser_found = True 
     if not subparser_found: 
      # insert default in first position, this implies no 
      # global options without a sub_parsers specified 
      if args is None: 
       sys.argv.insert(1, name) 
      else: 
       args.insert(0, name) 

argparse.ArgumentParser.set_default_subparser = set_default_subparser 

def do_main(args): 
    print 'main verbose', args.verbose 

def do_db(args): 
    print 'db verbose:', args.verbose 

parser = argparse.ArgumentParser() 
parser.add_argument('--verbose', action='store_true') 
parser.add_argument('--version', action='version', version='%(prog)s 2.0') 
subparsers = parser.add_subparsers() 
sp = subparsers.add_parser('main') 
sp.set_defaults(func=do_main) 
sp.add_argument('--verbose', action='store_true') 
sp = subparsers.add_parser('db') 
sp.set_defaults(func=do_db) 

parser.set_default_subparser('main') 
args = parser.parse_args() 

if hasattr(args, 'func'): 
    args.func(args) 

Метод set_default_subparser() является частью пакета ruamel.std.argparse.

4

Это, похоже, реализует основную идею дополнительного подпарамера. Мы разбираем стандартные аргументы, применимые ко всем подкомандам. Затем, если что-то осталось, мы вызываем парсер на остальных. Основными аргументами являются родительский элемент подкоманды, поэтому -h отображается правильно. Я планирую ввести интерактивное приглашение, если никаких подкоманд нет.

import argparse 

p1 = argparse.ArgumentParser(add_help = False)  
p1.add_argument(‘–flag1′) 

p2 = argparse.ArgumentParser(parents = [ p1 ]) 
s = p2.add_subparsers() 
p = s.add_parser(‘group’) 
p.set_defaults(group=True) 

(init_ns, remaining) = p1.parse_known_args() 

if remaining: 
    p2.parse_args(args = remaining, namespace=init_ns) 
else: 
    print(‘Enter interactive loop’) 

print(init_ns) 
Смежные вопросы