当前位置: 代码迷 >> python >> 使用Python / argparse创建可组合/分层命令行解析器
  详细解决方案

使用Python / argparse创建可组合/分层命令行解析器

热度:119   发布时间:2023-06-13 14:03:52.0

(问题的简化形式。)我正在编写一个涉及一些Python组件的API。 这些可能是功能,但具体来说,它们是对象。 我希望能够从命令行解析各种组件的选项。

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  (*)

如果你忘了我们想要前缀(以便能够添加多个相同类型的解析器)并且可能是命名空间(这样我们可以构建树状结构的命名空间来反映结构的结构),它看起来有点像argparseparents节点特征组件)。

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

问题似乎是argparse API基本上是在改变你的解析器,但查询它们更加困难 - 如果你直接将数据类型转换为解析器,你可以只是走这些对象。 如果用户编写了一堆函数来手动为解析器添加参数,我设法破解了一些add_argument ,但是每个add_argument调用必须接受一个前缀,整个事情变得非常难以理解并且可能是不可组合的。 (您可以以复制内部数据结构的某些部分为代价来对此进行抽象...)。 我还尝试将parsergroup对象子类化...

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

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

一些可能有助于构建更大解析器的想法:

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

现在arg1是对add_argument创建的Action对象的add_argument 我建议在交互式shell中执行此操作并查看其属性。 或至少打印其repr 您还可以尝试修改属性。 解析器“知道”关于参数的大多数内容都包含在这些actions 从某种意义上说,解析器是一个“包含”一堆“动作”的对象。

再看看:

parser._actions

这是解析器的主要操作列表,其中包括默认帮助以及您添加的帮助。

parents机制将Action引用从父级复制到子级。 请注意,它不会复制Action对象。 它还会重新创建参数组 - 但这些组仅用于对帮助行进行分组。 它们与解析无关。

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

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

我们在早期的SO问题中谈到了复合Namespace对象。 默认的argparse.Namespace类是一个带有repr方法的简单对象类。 解析器只使用hasattrgetattrsetattr ,尝试尽可能不具体。 您可以构建更精细的命名空间类。

您还可以自定义Action类。 这是大多数值插入命名空间的地方(尽管默认值在其他地方设置)。

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

您可以使用组合操作的概念来实现所需的功能。 您可以根据需要构建修改命名空间,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

参见示例: :