如何在Python中实现“嵌套”子命令?
在Python中使用cmdln实现“嵌套”子命令。
我不太确定自己用的术语是否正确。我正在尝试用cmdln来实现一个命令行工具,想要支持“嵌套”的子命令。这里有一个实际的例子:
git svn rebase
实现这个的最佳方法是什么呢?我在文档、这里和网上搜索了很多信息,但都没有找到有用的内容。(也许我用的搜索词不对。)
除了一个没有文档说明的功能可以自动实现这个,我最初的想法是让上一个子命令的处理器判断是否还有其他子命令,然后再次调用命令调度器。不过,我查看了cmdln的内部结构,发现调度器是一个私有方法,叫做_dispatch_cmd。接下来的想法是自己创建一个子子命令的调度器,但这似乎不太理想,而且会很麻烦。
任何帮助都会很感激。
4 个回答
我觉得在使用argparse中的子解析器时,有一点小限制。比如说,如果你有一套工具,它们可能有一些相似的选项,这些选项可能分布在不同的层级上。这种情况可能不常见,但如果你在写可插拔或模块化的代码时,可能会遇到。
我有一个例子。这个例子有点牵强,而且现在解释得不是很好,因为已经很晚了,但我还是试试:
Usage: tool [-y] {a, b}
a [-x] {create, delete}
create [-x]
delete [-y]
b [-y] {push, pull}
push [-x]
pull [-x]
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-x', action = 'store_true')
parser.add_argument('-y', action = 'store_true')
subparsers = parser.add_subparsers(dest = 'command')
parser_a = subparsers.add_parser('a')
parser_a.add_argument('-x', action = 'store_true')
subparsers_a = parser_a.add_subparsers(dest = 'sub_command')
parser_a_create = subparsers_a.add_parser('create')
parser_a_create.add_argument('-x', action = 'store_true')
parser_a_delete = subparsers_a.add_parser('delete')
parser_a_delete.add_argument('-y', action = 'store_true')
parser_b = subparsers.add_parser('b')
parser_b.add_argument('-y', action = 'store_true')
subparsers_b = parser_b.add_subparsers(dest = 'sub_command')
parser_b_create = subparsers_b.add_parser('push')
parser_b_create.add_argument('-x', action = 'store_true')
parser_b_delete = subparsers_b.add_parser('pull')
parser_b_delete.add_argument('-y', action = 'store_true')
print parser.parse_args(['-x', 'a', 'create'])
print parser.parse_args(['a', 'create', '-x'])
print parser.parse_args(['b', '-y', 'pull', '-y'])
print parser.parse_args(['-x', 'b', '-y', 'push', '-x'])
输出
Namespace(command='a', sub_command='create', x=True, y=False)
Namespace(command='a', sub_command='create', x=True, y=False)
Namespace(command='b', sub_command='pull', x=False, y=True)
Namespace(command='b', sub_command='push', x=True, y=True)
如你所见,很难分辨每个参数在链条上的具体位置。你可以通过给每个变量换个名字来解决这个问题。例如,你可以把'dest'设置为'x'、'a_x'、'a_create_x'、'b_push_x'等等,但这样做会很麻烦,也很难分开。
另一种方法是让ArgumentParser在到达子命令时停止,然后把剩下的参数交给另一个独立的解析器,这样它就可以生成不同的对象。你可以尝试使用'parse_known_args()',而不为每个子命令定义参数。不过,这样做不好,因为之前未解析的参数仍然会存在,可能会让程序感到困惑。
我觉得一个稍微简单但有用的变通方法是让argparse把以下参数当作字符串放在一个列表里。这可以通过把前缀设置为一个空终止符'\0'(或者其他一些“难以使用”的字符)来实现——如果前缀为空,代码会报错,至少在Python 2.7.3中是这样的。
例子:
parser = ArgumentParser()
parser.add_argument('-x', action = 'store_true')
parser.add_argument('-y', action = 'store_true')
subparsers = parser.add_subparsers(dest = 'command')
parser_a = subparsers.add_parser('a' prefix_chars = '\0')
parser_a.add_argument('args', type = str, nargs = '*')
print parser.parse_args(['-xy', 'a', '-y', '12'])
输出:
Namespace(args=['-y', '12'], command='a', x=True, y=True)
注意,它并没有消耗第二个-y
选项。然后你可以把结果'args'传递给另一个ArgumentParser。
缺点:
- 帮助信息可能处理得不好。需要做一些额外的变通。
- 遇到错误时可能很难追踪,需要额外的努力来确保错误信息正确关联。
- 使用多个ArgumentParser会有一点额外的开销。
如果有人对此有更多的看法,请告诉我。
我来得有点晚,不过我之前也遇到过很多次这种情况,发现用 argparse
来处理这件事挺麻烦的。这让我决定写一个 argparse
的扩展,叫做 arghandler,它专门支持这个功能——几乎不需要写代码就能实现子命令。
下面是一个例子:
from arghandler import *
@subcmd
def push(context,args):
print 'command: push'
@subcmd
def pull(context,args):
print 'command: pull'
# run the command - which will gather up all the subcommands
handler = ArgumentHandler()
handler.run()
argparse 这个工具让你使用子命令变得非常简单。