我有一个类似 git 的大型 CLI,其中包含许多命令、子命令和参数。但是,大多数 Argparse 教程仅显示使用单个文件的简单 CLI。这种方法有效,但导致模块庞大且难以维护。通过为每个命令创建一个类,传入命令容器(由
parser.add_subparsers
创建),并将命令添加到命令容器中,我部分成功地使代码库更加模块化。
这非常有效,直到我遇到一个包含很多子命令的命令。虽然我可以采用相同的方法并将子命令容器 (
command.add_subparsers
) 传递给每个子命令,但似乎更好的方法是反转控制并让每个命令和子命令 get (或创建和获取)各自的容器。这应该很简单,因为每个子命令都知道其父命令。例如,如果我有以下结构:
command1
command2
subcommand_A
subcommand_B
command3
我想添加 sucommand_B 执行以下操作:
parser.get_container(command="command2", subcommand="subcommand_B").add_parser(subcommand_B)
。
这似乎是可能的,但不推荐,因为容器深埋在 ArgumentParser 的私有属性中。
有没有一种好方法可以从解析器(即 ArgumentParser)中检索命令和子命令容器?如果没有,我还能如何使我的命令和子命令更加模块化?
我已经回答了很多关于
argparse
的问题,并尝试跟踪所有相关的错误/问题。尚未讨论多文件组织。某些第三方软件包可能会尝试扩展arpgarse
。几年前,我通过 pypi 存储库上的 argparse
包发现了 plac
。
ipython/jupyter
还使用(或至少习惯于)它自己的 arpgarse
变体,从 config
文件构建参数。并且 magics
使用类似 argparse 的解析器。
你看过
argparse.py
代码吗?它使用类、几个更抽象的“容器”类以及用户创建的 parser
和 action
类编写得很好。许多属性是“公共”的,但一些有用的属性也是“半私有”的(在Python的松散意义上)。
我会尝试用一个简单的案例来说明:
In [1]: import argparse
In [2]: parser = argparse.ArgumentParser()
In [3]: a1 = parser.add_argument('-f','--foo')
In [4]: sp = parser.add_subparsers(dest='cmd')
In [5]: sp1 = sp.add_parser('cmd1')
In [6]: a2 = sp1.add_argument('-b','--bar')
每个命令返回一个对象
解析器本身:
In [7]: parser
Out[7]: ArgumentParser(prog='ipykernel_launcher.py', usage=None, description=None, formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)
._actions
是一个有用的列表,其中包含我们创建的所有 Action
子类对象(包括自动帮助)。
In [8]: parser._actions
Out[8]:
[_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, required=False, help='show this help message and exit', metavar=None),
_StoreAction(option_strings=['-f', '--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None),
_SubParsersAction(option_strings=[], dest='cmd', nargs='A...', const=None, default=None, type=None, choices={'cmd1': ArgumentParser(prog='ipykernel_launcher.py cmd1', usage=None, description=None, formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)}, required=False, help=None, metavar=None)]
请注意,该列表包括我们分配给
subparsers
的 sp
操作。对于 parser
来说,sp
只是另一个位置参数。它的所有特殊行为都嵌入在它的子类定义中。
In [9]: sp
Out[9]: _SubParsersAction(option_strings=[], dest='cmd', nargs='A...', const=None, default=None, type=None, choices={'cmd1': ArgumentParser(prog='ipykernel_launcher.py cmd1', usage=None, description=None, formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)}, required=False, help=None, metavar=None)
子解析器是另一个
ArgumentParser
对象,有自己的 _actions
:
In [10]: sp1
Out[10]: ArgumentParser(prog='ipykernel_launcher.py cmd1', usage=None, description=None, formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)
In [11]: sp1._actions
Out[11]:
[_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, required=False, help='show this help message and exit', metavar=None),
_StoreAction(option_strings=['-b', '--bar'], dest='bar', nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None)]
因此,您可以通过查看属性(带或不带“_”标头的属性)来发现这些对象彼此“了解”的所有内容。
为了帮助格式化,所有操作都是
Action_group
的一部分。但这是在解析过程中使用的另一层组织(除了相互排斥的组)。
总而言之,
argparse.py
中没有任何旨在促进多文件组织的内容。但我想可以利用它的阶级组织来使这变得更容易。