2013-09-06 1 views
7

argparse은 그룹이나 파서를 자체 네임 스페이스로 구문 분석 할 수있는 기본 제공 기능을 제공합니까? 나는 어딘가에서 선택권을 놓치고 있어야하는 것처럼 느낀다.중첩 네임 스페이스가있는 argparse 하위 명령

편집 :이 예는 내 목표를 달성하는 파서를 구성하는 일을해야 정확히 아마 아니지만, 그것은 내가 지금까지 밖으로 일 것이었다. 내 특정 목표는 네임 스페이스 필드로 구문 분석되는 옵션 그룹을 서브 파서에 제공 할 수있게하는 것입니다. 부모님과 함께 가진 아이디어는 단순히이 같은 목적으로 공통 옵션을 사용하는 것이 었습니다.

예 :

$ python test.py command_a -foo bar -filter1 val 
Namespace(bar=None, common=None, filter1='val', filter2=None, foo='bar') 

하지만 난 정말 후 나는 무엇을 :의 다음

Namespace(bar=None, common=None, foo='bar', 
      filter=Namespace(filter1='val', filter2=None)) 

그리고 더 많은 그룹이 내가 분명히 무엇을 얻을

import argparse 

# Main parser 
main_parser = argparse.ArgumentParser() 
main_parser.add_argument("-common") 

# filter parser 
filter_parser = argparse.ArgumentParser(add_help=False) 
filter_parser.add_argument("-filter1") 
filter_parser.add_argument("-filter2") 

# sub commands 
subparsers = main_parser.add_subparsers(help='sub-command help') 

parser_a = subparsers.add_parser('command_a', help="command_a help", parents=[filter_parser]) 
parser_a.add_argument("-foo") 
parser_a.add_argument("-bar") 

parser_b = subparsers.add_parser('command_b', help="command_b help", parents=[filter_parser]) 
parser_b.add_argument("-biz") 
parser_b.add_argument("-baz") 

# parse 
namespace = main_parser.parse_args() 
print namespace 

이미 이름 공간에 파싱 된 옵션 :

Namespace(common=None, 
      foo='bar', bar=None, 
      filter=Namespace(filter1='val', filter2=None), 
      anotherGroup=Namespace(bazers='val'), 
      anotherGroup2=Namespace(fooers='val'), 
     ) 

나는 related question here을 찾았지만 맞춤 구문 분석과 관련이 있으며 정말로 특정한 상황만을 다루는 것으로 보입니다.

특정 그룹을 네임 스페이스 필드로 구문 분석하도록 argparse에 지시 할 수있는 옵션이 있습니까?

+0

: (니모닉 : --mod1-...는 "MOD1", 등의 옵션이 있습니다). 여러분이 작성한 것처럼'filter1'과'filter2'는 최상위 파서에 있고'filter'라는 이름의 자식 파서는 아닙니다. 어떻게 argparse 당신이 각 하위 파서의 자식으로 행동하기를 원한다는 것을 알 수 있습니까? – abarnert

+0

@abarnert : 질문에 따라 예제를 다시 포맷해야합니다. 왜냐하면 당신이 지적한 것처럼 실제로 내가 함께 모은 구조는 적절하지 않기 때문입니다. 내 목표는 실제로 서브 그룹에 옵션 그룹을 적용하고 네임 스페이스로 구문 분석하도록하는 것입니다. 그들이 공통적 일 수 있다면 좋을 것입니다. 그래서 부모 구조를 사용해 보았습니다. – jdi

+0

그래서'pip','git' 등과 같이 최상위 글로벌 옵션과 각 하위 명령에 고유 한 옵션 외에도 여러 하위 명령 (예 : '--verbose','--upgrade','--user' 옵션을 각각'pip'에 추가), 옵션 그룹을 암시 적으로 만드는 대신에 직접 공유를 표현할 수 있어야합니다 (옵션 그룹을 여러 개의 서브 파서에 복사) ? – abarnert

답변

9

선택한 인수를 자체적으로 namespace에 넣는 데 중점을두고 하위 파서 (및 상위)의 사용이 문제와 관련되는 경우이 사용자 지정 동작은 트릭을 수행 할 수 있습니다.

class GroupedAction(argparse.Action):  
    def __call__(self, parser, namespace, values, option_string=None): 
     group,dest = self.dest.split('.',2) 
     groupspace = getattr(namespace, group, argparse.Namespace()) 
     setattr(groupspace, dest, values) 
     setattr(namespace, group, groupspace) 

group 이름을 지정하는 방법에는 여러 가지가 있습니다. 액션을 정의 할 때 인수로 전달 될 수 있습니다. 그것은 매개 변수로 추가 될 수 있습니다. 여기서 나는 dest에서 구문 분석을 선택했습니다 (따라서 namespace.filter.filter1filter.filter1의 값을 얻을 수 있습니다).

# Main parser 
main_parser = argparse.ArgumentParser() 
main_parser.add_argument("-common") 

filter_parser = argparse.ArgumentParser(add_help=False) 
filter_parser.add_argument("--filter1", action=GroupedAction, dest='filter.filter1', default=argparse.SUPPRESS) 
filter_parser.add_argument("--filter2", action=GroupedAction, dest='filter.filter2', default=argparse.SUPPRESS) 

subparsers = main_parser.add_subparsers(help='sub-command help') 

parser_a = subparsers.add_parser('command_a', help="command_a help", parents=[filter_parser]) 
parser_a.add_argument("--foo") 
parser_a.add_argument("--bar") 
parser_a.add_argument("--bazers", action=GroupedAction, dest='anotherGroup.bazers', default=argparse.SUPPRESS) 
... 
namespace = main_parser.parse_args() 
print namespace 

나는 default=argparse.SUPPRESS이 때문에 bazers=None 항목이 주 네임 스페이스에없는 추가했다.

결과 : 당신이 중첩 된 네임 스페이스의 기본 항목을해야하는 경우

>>> python PROG command_a --foo bar --filter1 val --bazers val 
Namespace(anotherGroup=Namespace(bazers='val'), 
    bar=None, common=None, 
    filter=Namespace(filter1='val'), 
    foo='bar') 

, 당신은 손 전 네임 스페이스 정의 할 수 있습니다 : 제외, 이전

filter_namespace = argparse.Namespace(filter1=None, filter2=None) 
namespace = argparse.Namespace(filter=filter_namespace) 
namespace = main_parser.parse_args(namespace=namespace) 

결과를위한 :

filter=Namespace(filter1='val', filter2=None) 
+0

예. 나는 이것이 argparse (커스텀 액션)의 기능을 사용하여 실제로 나의 목표를 해결하기 때문에 이전에 받아 들여진 대답 대신이 것을 받아 들일 것이다. 그리고 사실 ... 도트 표기법 "dest"는 내가 처음에 기대했던 것입니다. 감사! – jdi

+1

GroupedAction을 추가하여 최상위 원본 속성을 정리하고 옵션에서 그룹/필드를 도출했습니다. http://pastebin.com/qgQBBuvP – jdi

+0

@jdi : 이것은 정확히 무엇입니까? 나는 아마 서브 클래 싱하여 커스텀 파싱으로 argparse를 확장하는 것이 더 좋을 것이라고 말했을 때; 나는 당신의 질문이 당신이 그것을 이런 방식으로하고 싶지 않았다는 것을 암시했기 때문에 그렇지 않은 방법으로 만 보여주었습니다. 나는 이것이 대단한 대답이라는 것에 동의한다. – abarnert

5

당신이 무엇을 요구하고 있는지 잘 모르겠지만, 당신이 원하는 것은 argument group 또는 sub-command이 하위 네임 스페이스에 인수를 넣는 것입니다.

내가 아는 한, argparse은 기본적으로이 작업을 수행하지 않습니다. 그러나 실제로는 결과를 후 처리하여 수행하기가 어렵지 않습니다. 한 번만 살펴보고 싶다면 말입니다. (I는 ArgumentParser을 하위 클래스를 할 경우에도 쉽게 추측하고있어,하지만 당신은 명시 적으로 그렇게하고 싶지 않아했다, 그래서 나는 그것을 시도하지 않았다.) 이제

parser = argparse.ArgumentParser() 
parser.add_argument('--foo') 
breakfast = parser.add_argument_group('breakfast') 
breakfast.add_argument('--spam') 
breakfast.add_argument('--eggs') 
args = parser.parse_args() 

, 모든 목록을 breakfast 옵션에 대한 목적지는 다음과 같습니다 args

[action.dest for action in breakfast._group_actions] 

그리고 키 - 값 쌍은 다음과 같습니다 그래서

args._get_kwargs() 

, 우리가 모두 일치하는 사람을 움직입니다 할 수 있습니다. 우리의 네임 스페이스를 만들기 위해 사전을 만드는 경우는 좀 더 쉽게 알 수있을 것입니다 :

breakfast_options = [action.dest for action in breakfast._group_actions] 
top_names = {name: value for (name, value) in args._get_kwargs() 
      if name not in breakfast_options} 
breakfast_names = {name: value for (name, value) in args._get_kwargs() 
        if name in breakfast_options} 
top_names['breakfast'] = argparse.Namespace(**breakfast_names) 
top_namespace = argparse.Namespace(**top_names) 

그리고 그것 뿐이다을; 이 경우

Namespace(breakfast=Namespace(eggs=None, spam='7'), foo='bar') 
물론

, 우리는 하나 개의 정적 그룹을 가지고 : 같은 top_namespace 보인다. 좀 더 일반적인 해결책을 원한다면 어떨까요? 쉬운. parser._action_groups은 모든 그룹의 목록이지만 처음 두 개는 전역 위치 그룹과 키워드 그룹입니다. 따라서 parser._action_groups[2:]을 반복하고 위의 breakfast에 대해 수행 한 작업과 동일한 작업을 수행하십시오.


무엇 하위 명령 대신 그룹에 대한? 비슷하지만 세부 사항은 다릅니다. 각각의 subparser 객체를 보관하고 있다면, 다른 모든 것일 수 있습니다. ArgumentParser. 그렇지 않은 경우 subparsers 개체를 유지했지만 Action의 특수 유형 인 choices은 키가 하위 파서 이름이고 하위 개체가 값인 하위 개체입니다. 당신이 어느 쪽도 안 지키지 않았다면 ... parser._subparsers에서 시작해서 거기에서 알아 내십시오.

어쨌든 이동하려는 이름을 찾는 방법과 이동할 위치를 알고 나면 그룹과 동일합니다. 전역 인수 및/또는 그룹과 subparser 특정 인수 및/또는 그룹, 여러 subparsers 공유하는 일부 그룹에 추가하여, 가지고있는 경우에


... 다음 개념적으로 각 subparser 때문에, 까다로운 도착 같은 그룹에 대한 참조로 끝나고 그 그룹으로 이동할 수 없습니다. 다행스럽게도 정확히 하나의 하위 파서 만 취급하므로 다른 하위 파서를 무시하고 선택된 하위 파서 아래의 공유 그룹을 이동하면됩니다 (이 아닌 하위 그룹은 선택한 하위 파서에, 상단에 놓거나 버리거나 임의로 하나의 서브 파서를 선택).

+0

이 질문에 대한 답변이 제 질문에 거의 답하고 있습니다. 처음에는 그룹을 보면서 시작했는데, 도움의 측면에서 그룹화 된 것 같습니다. 따라서 수동으로 약간의 후 처리가 필요하다고 설명 했으므로 괜찮습니다. argparse에서 필요한 접근법을 보여주는 예제와 같은 것을 볼 필요가 있습니다. 감사! – jdi

+0

@jdi : 대답에서 말했듯이 postprocessing 대신 subclassing을 통해 argparse를 확장하는 것이 여기에서 더 쉬울 수도 있습니다. 그룹 객체는 파서 개체를 거의 변경하지 않고도 쉽게 수행 할 수있는 기능입니다. 그리고 그것은 아마도 더 관용적 일 것입니다. 그러나 어느 쪽이 더 편하게 느껴지 든 괜찮을 것입니다. – abarnert

0

이 스크립트에서는 argparse._SubParsersAction의 __call__ 메서드를 수정했습니다. namespace을 서브 파서에 전달하는 대신 새 서브 파서를 전달합니다. 그런 다음 주 namespace에 추가합니다. 나는 __call__의 3 줄만 변경합니다.

import argparse 

def mycall(self, parser, namespace, values, option_string=None): 
    parser_name = values[0] 
    arg_strings = values[1:] 

    # set the parser name if requested 
    if self.dest is not argparse.SUPPRESS: 
     setattr(namespace, self.dest, parser_name) 

    # select the parser 
    try: 
     parser = self._name_parser_map[parser_name] 
    except KeyError: 
     args = {'parser_name': parser_name, 
       'choices': ', '.join(self._name_parser_map)} 
     msg = _('unknown parser %(parser_name)r (choices: %(choices)s)') % args 
     raise argparse.ArgumentError(self, msg) 

    # CHANGES 
    # parse all the remaining options into a new namespace 
    # store any unrecognized options on the main namespace, so that the top 
    # level parser can decide what to do with them 
    newspace = argparse.Namespace() 
    newspace, arg_strings = parser.parse_known_args(arg_strings, newspace) 
    setattr(namespace, 'subspace', newspace) # is there a better 'dest'? 

    if arg_strings: 
     vars(namespace).setdefault(argparse._UNRECOGNIZED_ARGS_ATTR, []) 
     getattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) 

argparse._SubParsersAction.__call__ = mycall 

# Main parser 
main_parser = argparse.ArgumentParser() 
main_parser.add_argument("--common") 

# sub commands 
subparsers = main_parser.add_subparsers(dest='command') 

parser_a = subparsers.add_parser('command_a') 
parser_a.add_argument("--foo") 
parser_a.add_argument("--bar") 

parser_b = subparsers.add_parser('command_b') 
parser_b.add_argument("--biz") 
parser_b.add_argument("--baz") 

# parse 
input = 'command_a --foo bar --bar val --filter extra'.split() 
namespace = main_parser.parse_known_args(input) 
print namespace 

input = '--common test command_b --biz bar --baz val'.split() 
namespace = main_parser.parse_args(input) 
print namespace 

이 생성됩니다

(Namespace(command='command_a', common=None, 
    subspace=Namespace(bar='val', foo='bar')), 
['--filter', 'extra']) 

Namespace(command='command_b', common='test', 
    subspace=Namespace(baz='val', biz='bar')) 

내가 다시 주 분석기에 전달하는 방법을 추가 문자열 테스트 parse_known_args을 사용했다.

이 네임 스페이스 변경에 아무 것도 추가하지 않기 때문에 parents 내용이 삭제되었습니다. 이것은 여러 subparser가 사용하는 인수 집합을 정의하는 편리한 방법 일뿐입니다. argparseparents을 통해 추가 된 인수가 기록되지 않고 직접 추가되었습니다. 그룹화 도구가 아닙니다.

argument_groups 많은 도움이되지 않습니다. 이것들은 도움말 포맷터에서 사용하지만 parse_args이 아닙니다.

(__call__을 재 할당하는 대신) _SubParsersAction을 서브 클래스로 지정할 수 있지만 main_parse.register을 변경해야합니다.

+0

이것은 원숭이 패치를 통해 구문 분석하는 방법에 대한 멋진 예제입니다 ... 단점은 알려진/알려지지 않은 args 접근법을 사용한다는 것입니다. 즉, 필터가 argparse를 통해 문서화되거나 관리되지 않음을 의미합니다. – jdi

4

Action 하위 클래스로 중첩하는 것은 한 가지 유형의 액션에 적합하지만 if 여러 유형 (store, store true, append 등)을 서브 클래스 화해야합니다. 다른 아이디어 - 서브 클래스 Namespace가 있습니다. 같은 종류의 이름을 나누고 setattr을 수행하지만, Action이 아닌 네임 스페이스에서 수행하십시오. 그런 다음 새 클래스의 인스턴스를 만들고이를 parse_args에 전달합니다.

class Nestedspace(argparse.Namespace): 
    def __setattr__(self, name, value): 
     if '.' in name: 
      group,name = name.split('.',1) 
      ns = getattr(self, group, Nestedspace()) 
      setattr(ns, name, value) 
      self.__dict__[group] = ns 
     else: 
      self.__dict__[name] = value 

p = argparse.ArgumentParser() 
p.add_argument('--foo') 
p.add_argument('--bar', dest='test.bar') 
print(p.parse_args('--foo test --bar baz'.split())) 

ns = Nestedspace() 
print(p.parse_args('--foo test --bar baz'.split(), ns)) 
p.add_argument('--deep', dest='test.doo.deep') 
args = p.parse_args('--foo test --bar baz --deep doodod'.split(), Nestedspace()) 
print(args) 
print(args.test.doo) 
print(args.test.doo.deep) 

생산 :

Namespace(foo='test', test.bar='baz') 
Nestedspace(foo='test', test=Nestedspace(bar='baz')) 
Nestedspace(foo='test', test=Nestedspace(bar='baz', doo=Nestedspace(deep='doodod'))) 
Nestedspace(deep='doodod') 
doodod 

__getattr__을이 네임 스페이스 (계수와 같은 활동을 위해 필요하고 추가) 될 수있다 : 나는 몇 가지 다른 옵션을 제안했습니다

def __getattr__(self, name): 
    if '.' in name: 
     group,name = name.split('.',1) 
     try: 
      ns = self.__dict__[group] 
     except KeyError: 
      raise AttributeError 
     return getattr(ns, name) 
    else: 
     raise AttributeError 

하지만, 같은 이게 최고야. 파서가 아닌 네임 스페이스에 속한 저장 세부 정보를 저장합니다.

+0

아 멋지다. 네임 스페이스를 서브 클래 싱하는 것조차 생각조차하지 않았습니다. 나는 일반적으로 이것을 좋아하지만, 마지막 대답부터 필드와 일치하는 기본 메타 바를 설정하고 사용자 정의 액션을 등록하기 위해 ArgumentGroup과 같은 것을 서브 클래 싱하는 것이 도움이된다는 것을 알게되었습니다. 나는이 커스텀 네임 스페이스가 조합되어 있음을 확신한다. – jdi

0

abarnert의 답변에서 시작하여 비슷한 옵션 이름을 가진 여러 구성 그룹을 처리하는 다음 MWE ++ ;-)을 작성했습니다.

#!/usr/bin/env python2 
import argparse, re 

cmdl_skel = { 
    'description'  : 'An example of multi-level argparse usage.', 
    'opts'    : { 
     '--foo' : { 
      'type' : int, 
      'default' : 0, 
      'help' : 'foo help main', 
     }, 
     '--bar' : { 
      'type' : str, 
      'default' : 'quux', 
      'help' : 'bar help main', 
     }, 
    }, 
    # Assume your program uses sub-programs with their options. Argparse will 
    # first digest *all* defs, so opts with the same name across groups are 
    # forbidden. The trick is to use the module name (=> group.title) as 
    # pseudo namespace which is stripped off at group parsing 
    'groups' : [ 
     { 'module'  : 'mod1', 
      'description' : 'mod1 description', 
      'opts'   : { 
       '--mod1-foo, --mod1.foo' : { 
        'type' : int, 
        'default' : 0, 
        'help' : 'foo help for mod1' 
       }, 
      }, 
     }, 
     { 'module'  : 'mod2', 
      'description' : 'mod2 description', 
      'opts'   : { 
       '--mod2-foo, --mod2.foo' : { 
        'type' : int, 
        'default' : 1, 
        'help' : 'foo help for mod2' 
       }, 
      }, 
     }, 
    ], 
    'args'    : { 
     'arg1' : { 
      'type' : str, 
      'help' : 'arg1 help', 
     }, 
     'arg2' : { 
      'type' : str, 
      'help' : 'arg2 help', 
     }, 
    } 
} 


def parse_args(): 
    def _parse_group (parser, opt, **optd): 
     # digest variants 
     optv = re.split('\s*,\s*', opt) 
     # this may rise exceptions... 
     parser.add_argument(*optv, **optd) 

    errors = {} 
    parser = argparse.ArgumentParser(description=cmdl_skel['description'], 
       formatter_class=argparse.ArgumentDefaultsHelpFormatter) 

    # it'd be nice to loop in a single run over zipped lists, but they have 
    # different lenghts... 
    for opt in cmdl_skel['opts'].keys(): 
     _parse_group(parser, opt, **cmdl_skel['opts'][opt]) 

    for arg in cmdl_skel['args'].keys(): 
     _parse_group(parser, arg, **cmdl_skel['args'][arg]) 

    for grp in cmdl_skel['groups']: 
     group = parser.add_argument_group(grp['module'], grp['description']) 
     for mopt in grp['opts'].keys(): 
      _parse_group(group, mopt, **grp['opts'][mopt]) 

    args = parser.parse_args() 

    all_group_opts = [] 
    all_group_names = {} 
    for group in parser._action_groups[2:]: 
     gtitle = group.title 
     group_opts = [action.dest for action in group._group_actions] 
     all_group_opts += group_opts 
     group_names = { 
      # remove the leading pseudo-namespace 
      re.sub("^%s_" % gtitle, '', name) : value 
       for (name, value) in args._get_kwargs() 
        if name in group_opts 
     } 
     # build group namespace 
     all_group_names[gtitle] = argparse.Namespace(**group_names) 

    # rebuild top namespace 
    top_names = { 
     name: value for (name, value) in args._get_kwargs() 
      if name not in all_group_opts 
    } 
    top_names.update(**all_group_names) 
    top_namespace = argparse.Namespace(**top_names) 

    return top_namespace 


def main(): 
    args = parse_args() 

    print(str(args)) 
    print(args.bar) 
    print(args.mod1.foo) 


if __name__ == '__main__': 
    main() 

그럼 당신은 이런 식으로 호출 할 수 있습니다 당신은이 작업을 기대하는 방법을 모르겠어요

$ ./argparse_example.py one two --bar=three --mod1-foo=11231 --mod2.foo=46546 
Namespace(arg1='one', arg2='two', bar='three', foo=0, mod1=Namespace(foo=11231), mod2=Namespace(foo=46546)) 
three 
11231 
관련 문제