2015-10-19 25 views
2

(这是一个简化的问题。)我正在编写一个涉及Python组件的API。这些可能是功能,但具体来说,让我们说他们是对象。我希望能够从命令行解析各种组件的选项。使用Python/argparse创建可组合/分层命令行分析器

from argparse import ArgumentParser 

class Foo(object): 
    def __init__(self, foo_options): 
     """do stuff with options""" 

    """...""" 

class Bar(object): 
    def __init__(sef, bar_options): 
     """...""" 

def foo_parser(): 
    """(could also be a Foo method)""" 
    p = ArgumentParser() 
    p.add_argument('--option1') 
    #... 
    return p 

def bar_parser(): "..." 

但现在我希望能够建立更大的组件:

def larger_component(options): 
    f1 = Foo(options.foo1) 
    f2 = Foo(options.foo2) 
    b = Bar(options.bar) 
    # ... do stuff with these pieces 

精细。但是如何编写适当的解析器?我们不妨这样的事情:

def larger_parser(): # probably need to take some prefix/ns arguments 
    # general options to be overridden by p1, p2 
    # (this could be done automagically or by hand in `larger_component`): 
    p = foo_parser(prefix=None,   namespace='foo') 
    p1 = foo_parser(prefix='first-foo-', namespace='foo1') 
    p2 = foo_parser(prefix='second-foo-', namespace='foo2') 
    b = bar_parser() 
    # (you wouldn't actually specify the prefix/namespace twice:) 
    return combine_parsers([(p1, namespace='foo1', prefix='first-foo-'), 
          (p2,...),p,b]) 

larger_component(larger_parser().parse_args()) 
# CLI should accept --foo1-option1, --foo2-option1, --option1 (*) 

这看起来有点像​​的parents功能,如果你忘了我们想要的前缀(以便能够添加相同类型的多个解析器) 和可能命名空间(以便我们可以构建树形结构的命名空间以反映组件的结构)。

当然,我们希望large_component和larger_parser以相同的方式组合,并且传递给某个组件的名称空间对象应始终具有相同的内部形状/命名结构。

麻烦似乎是在​​API基本上是关于变异的你分析器,但查询它们是比较困难的 - 如果你直接打开一个 数据类型为解析器,你可以只走这些对象。如果用户编写了一堆函数以手动添加参数给解析器,但是每个add_argument调用都必须带一个前缀,并且整个事情变得非常难以理解,并且可能不可组合,所以我设法破解了某些有些作用。 (你可以通过复制内部数据结构的某些部分来对此进行抽象...)。我也试图子类parsergroup对象...

你可以想象这可能使用更多的代数CLI-解析API是可能的,但我不认为重写​​这里是一个很好的解决方案。

有没有一种已知的/直接的方法来做到这一点?

+0

你能证明你想使用一个命令行的一个例子?起初,它看起来像你想'subparsers'(这是argparse默认提供的),但我不能100%确定它是否适合你的需求。 – mgilson

+0

'subparsers'似乎是关于解析器(解析器A,或B或...)的“总和”,而我想要一个“产品”(所有选项可同时使用)。 – Fixnum

+0

有关示例,请参阅问题中用'(*)'指示的行。 – Fixnum

回答

1

一些想法,可以帮助您构成更大的解析器:

parser = argparse.ArgumentParser(...) 
arg1 = parser.add_argument('--foo',...) 

现在arg1是由add_argument创建的Action对象的引用。我建议在一个交互式shell中做这件事,看看它的属性。或者至少打印其repr。您也可以尝试修改属性。 解析器“知道”有关参数的大部分内容都包含在这些actions中。从某种意义上来说,解析器是一个“包含”一堆“动作”的对象。

也看在:

parser._actions 

这是行动,其中将包括默认的帮助,以及您添加的那些解析器的主列表。

parents机制将父母引用的Action副本复制到孩子。请注意,它不会制作Action对象的副本。它还重新创建了论证组 - 但这些组仅仅用于对帮助线进行分组。它们与解析无关。

args1, extras = parser.parse_known_args(argv, namespace) 

在处理多个解析器时非常有用。有了它,每个解析器都可以处理它所知道的参数,并将剩下的参数传递给其他人。尝试理解该方法的输入和输出。

我们已经在之前的SO问题中讨论过合成Namespace对象。默认的argparse.Namespace类是一个带有repr方法的简单对象类。解析器只是使用hasattr,getattrsetattr,试图尽可能非特定。你可以构造一个更复杂的命名空间类。

argparse subcommands with nested namespaces

您还可以自定义Action类。这就是大部分值插入到名字空间的地方(尽管默认值是在别处设置的)。

IPython使用​​,既用于主呼叫,又用于内部用于magic命令。它构造了来自config文件的许多参数。因此,可以使用默认配置,自定义配置或通过命令行参数在最后时刻设置许多值。

0

您可能可以使用组合动作的概念来实现所需的功能。你可以建立一个修改的命名空间,DEST,等等,因为你需要的操作,然后用它们组合:

def compose_actions(*actions): 
    """Compose many argparse actions into one callable action. 

    Args: 
     *actions: The actions to compose. 

    Returns: 
     argparse.Action: Composed action. 
    """ 
    class ComposableAction(argparse.Action): 
     def __call__(self, parser, namespace, values, option_string=None): 
      for action in actions: 
       action(option_string, self.dest).__call__(parser, 
                  namespace, 
                  values, 
                  option_string) 
    return ComposableAction 

见例如:https://gist.github.com/mnm364/edee068a5cebbfac43547b57b7c842f1