具有嵌套命名空间的argparse子命令

jdi*_*jdi 12 python namespaces argparse

argparse是否提供内置工具,使其将组或解析器解析为自己的命名空间?我觉得我必须在某个地方错过一个选项.

编辑:这个例子可能不是我应该做的,以构建解析器来实现我的目标,但这是我迄今为止所做的.我的具体目标是能够为子解析器提供被解析为命名空间字段的选项组.我与父母的想法只是为了同样的目的使用共同的选项.

例:

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
Run Code Online (Sandbox Code Playgroud)

这是我得到的,显然:

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

但这就是我真正追求的:

Namespace(bar=None, common=None, foo='bar', 
          filter=Namespace(filter1='val', filter2=None))
Run Code Online (Sandbox Code Playgroud)

然后,更多的选项组已经解析为名称空间:

Namespace(common=None, 
          foo='bar', bar=None,  
          filter=Namespace(filter1='val', filter2=None),
          anotherGroup=Namespace(bazers='val'),
          anotherGroup2=Namespace(fooers='val'),
          )
Run Code Online (Sandbox Code Playgroud)

我在这里找到了一个相关的问题,但它涉及一些自定义解析,似乎只涵盖了一个非常具体的情况.

是否有一个选项可以告诉argparse将某些组解析为命名空间字段?

hpa*_*ulj 12

如果重点只是将选定的参数放在自己的参数中namespace,并且subparsers(和parent)的使用是问题的偶然,那么这个自定义操作可能会起作用.

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)
Run Code Online (Sandbox Code Playgroud)

有多种方法可以指定group名称.它可以在定义Action时作为参数传递.它可以作为参数添加.在这里,我选择从中解析它dest(因此namespace.filter.filter1可以得到它的值filter.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
Run Code Online (Sandbox Code Playgroud)

我必须添加,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')
Run Code Online (Sandbox Code Playgroud)

如果您需要嵌套命名空间中的默认条目,则可以手动定义命名空间:

filter_namespace = argparse.Namespace(filter1=None, filter2=None)
namespace = argparse.Namespace(filter=filter_namespace)
namespace = main_parser.parse_args(namespace=namespace)
Run Code Online (Sandbox Code Playgroud)

结果与以前一样,除了:

filter=Namespace(filter1='val', filter2=None)
Run Code Online (Sandbox Code Playgroud)

  • 我对您的GroupedAction进行了一些添加,以使其清理顶级原始属性,还可以选择从选项中导出组/字段:http://pastebin.com/qgQBBuvP (2认同)

aba*_*ert 8

我不完全确定你在问什么,但我认为你想要的是一个参数组子命令将其参数放入一个子命名空间.

据我所知,argparse开箱即可做到这一点.但是,只要你愿意在底层挖掘一下,通过后处理结果真的不难做到.(我猜它通过子类化更容易做到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()
Run Code Online (Sandbox Code Playgroud)

现在,breakfast选项的所有目的地列表是:

[action.dest for action in breakfast._group_actions]
Run Code Online (Sandbox Code Playgroud)

键值对args是:

args._get_kwargs()
Run Code Online (Sandbox Code Playgroud)

所以,我们所要做的就是移动匹配的那些.如果我们构造字典来创建名称空间,那将会更容易:

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)
Run Code Online (Sandbox Code Playgroud)

就是这样; top_namespace好像:

Namespace(breakfast=Namespace(eggs=None, spam='7'), foo='bar')
Run Code Online (Sandbox Code Playgroud)

当然在这种情况下,我们有一个静态组.如果您想要更通用的解决方案怎么办?简单.parser._action_groups是所有组的列表,但前两个是全局位置和关键字组.所以,只需迭代parser._action_groups[2:],并为你为breakfast上面做的每一个做同样的事情.


子命令而不是组怎么样?类似,但细节不同.如果你保留在每个subparser物体周围,那就完全是另一个ArgumentParser.如果没有,但你保留了subparsers对象,它是一种特殊类型Action,它choices是一个dict,其键是subparser名称,其值是subparser本身.如果你两个都没有...... parser._subparsers从那里开始并弄明白.

无论如何,一旦您知道如何找到要移动的名称以及要移动它们的位置,它就与组相同.


除了全局args和/或组以及特定于subparser的args和/或组之外,还有一些由多个subparser共享的组...然后从概念上讲它会变得棘手,因为每个subparser最终会引用相同的小组,你不能把它移到他们身上.但幸运的是,您只处理一个subparser(或者没有),因此您可以忽略其他子分析器并移动所选子分析器下的任何共享组(以及所选子分析器中不存在的任何组,或者离开在顶部,或扔掉,或任意选择一个subparser).


hpa*_*ulj 7

Action子类嵌套适用于一种类型的Action,但如果您需要子类化几种类型(存储,存储true,追加等),则会很麻烦.这是另一个想法 - 子类命名空间.执行相同类型的名称split和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)
Run Code Online (Sandbox Code Playgroud)

生产:

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
Run Code Online (Sandbox Code Playgroud)

__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
Run Code Online (Sandbox Code Playgroud)

我已经提出了其他几个选项,但最好这样.它将存储详细信息放在命名空间中,而不是解析器中.