【问题标题】:Creating composable/hierarchical command-line parsers using Python/argparse使用 Python/argparse 创建可组合/分层的命令行解析器
【发布时间】:2016-01-18 09:04:54
【问题描述】:

(问题的简化形式。)我正在编写一个涉及一些 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  (*)

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

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

问题似乎在于argparse API 基本上是关于改变你的解析器,但查询它们更困难 - 如果你转过 直接将数据类型导入解析器,您可以只遍历这些对象。如果用户编写一堆函数来手动向解析器添加参数,我设法破解了一些有些工作的东西,但是每个add_argument 调用都必须带一个前缀,整个事情变得非常难以理解并且可能不可组合。 (您可以以复制内部数据结构的某些部分为代价对其进行抽象......)。我还尝试继承 parser 和 group 对象...

您可以想象这可能使用更代数的 CLI 解析 API 来实现,但我不认为重写 argparse 在这里是一个好的解决方案。

是否有已知/直接的方法可以做到这一点?

【问题讨论】:

  • 您能展示一个您想使用的命令行示例吗?乍一看,您似乎想要subparsers(argparse 默认提供),但我不能 100% 确定这是否适合您的需求。
  • subparsers 似乎是解析器的“总和”(解析器 A、B 或 ...),而我想要一个“产品”(所有选项同时可用)。
  • 例如,请参阅我在问题中用(*) 表示的行。

标签: python command-line-arguments argparse composition


【解决方案1】:

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

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

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

还请看:

parser._actions

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

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

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

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

我们在前面的 SO 问题中讨论了复合 Namespace 对象。默认的argparse.Namespace 类是一个带有repr 方法的简单对象类。解析器只使用hasattr、getattr 和setattr,尽量做到不具体。您可以构造一个更精细的命名空间类。

argparse subcommands with nested namespaces

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

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

【讨论】:

    【解决方案2】:

    您也许可以使用组合动作的概念来实现您需要的功能。您可以根据需要构建修改命名空间、目标等的操作,然后将它们组合成:

    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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-13
      • 2020-09-06
      • 2013-02-27
      • 2016-04-10
      • 1970-01-01
      • 2015-07-11
      • 2012-05-13
      相关资源
      最近更新 更多