我应该如何实现“嵌套” Python 中的子命令?

发布于 2024-12-21 06:53:10 字数 455 浏览 1 评论 0原文

使用 cmdln 在 Python 中实现“嵌套”子命令。

我不确定我在这里使用的术语是否正确。我正在尝试使用 cmdln 实现一个允许“嵌套”子命令的命令行工具。这是一个现实世界的例子:

git svn rebase

实现这个的最佳方法是什么?我一直在文档、此处和整个网络中搜索有关此问题的更多信息,但一无所获。 (也许我使用了错误的术语进行搜索。)

由于没有自动执行此操作的未记录功能,我最初的想法是让前一个子命令处理程序确定存在另一个子命令并再次调度命令调度程序。我已经查看了 cmdln 的内部结构,并且调度程序是一个私有方法 _dispatch_cmd。我的下一个想法是创建我自己的子命令调度程序,但这似乎不太理想且混乱。

任何帮助将不胜感激。

Implementing "nested" subcommands in Python with cmdln.

I'm not sure I'm using the right terminology here. I'm trying to implement a commandline tool using cmdln that allows for "nested" subcommands. Here is a real world example:

git svn rebase

What is the best way of implementing this? I've been searching for more information on this in the doc, here and the web at large, but have come up empty. (Perhaps I was searching with the wrong terms.)

Short of an undocumented feature that does this automatically, my initial thought was to have the previous subcommand handler determine that there is another subcommand and dispatch the command dispatcher again. I've looked at the internals of cmdln though and the dispatcher is a private method, _dispatch_cmd. My next thought is to create my own sub-sub-command dispatcher, but that seems less than ideal and messy.

Any help would be appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

百变从容 2024-12-28 06:53:11

argparse 使子命令变得非常容易。

argparse makes sub-commands very easy.

神魇的王 2024-12-28 06:53:11

我觉得 argparse 中的 sub_parsers 有一个轻微的限制,如果说,你有一套工具,可能有类似的选项,可能分布在不同的级别。这种情况可能很少见,但如果您正在编写可插入/模块化代码,则可能会发生这种情况。

我有下面的例子。这是牵强的,目前还没有很好地解释,因为已经很晚了,但它是这样的:

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。

缺点:

  • 帮助可能处理不好。必须对此采取更多解决方法
  • 遇到的错误可能很难跟踪,并且需要一些额外的努力来确保错误消息正确链接。
  • 与多个 ArgumentParsers 相关的开销稍多一些。

如果有人对此有更多意见,请告诉我。

I feel like there's a slight limitation with sub_parsers in argparse, if say, you have a suite of tools that might have similar options that might spread across different levels. It might be rare to have this situation, but if you're writing pluggable / modular code, it could happen.

I have the following example. It is far-fetched and not well explained at the moment because it is quite late, but here it goes:

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'])

Output

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)

As you can see, it is hard to distinguish where along the chain each argument was set.
You could solve this by changing the name for each variable. For example, you could set 'dest' to 'x', 'a_x', 'a_create_x', 'b_push_x', etc., but that would be painful and hard to separate out.

An alternative would be to have the ArgumentParser stop once it reaches a subcommand and pass the remaining arguments off to another, independent parser, so it could generates separate objects.
You can try to achieve that by using 'parse_known_args()' and not defining arguments for each subcommand. However, that would not be good because any un-parsed arguments from before would still be there and might confuse the program.

I feel a slightly cheap, but useful workaround is to have argparse interpret the following arguments as strings in a list. This can be done by setting the prefix to a null-terminator '\0' (or some other 'hard-to-use' character) - if the prefix is empty, the code will throw an error, at least in Python 2.7.3.

Example:

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'])

Output:

Namespace(args=['-y', '12'], command='a', x=True, y=True)

Note that it does not consume the second -y option.
You can then pass the result 'args' to another ArgumentParser.

Drawbacks:

  • Help might not be handled well. Would have to make some more workaround with this
  • Encountering errors might be hard to trace and require some additional effort to make sure error messages are properly chained.
  • A little bit more overhead associated with the multiple ArgumentParsers.

If anybody has more input on this, please let me know.

沫尐诺 2024-12-28 06:53:11

2020 年更新!
Click 库更易于使用
Fire 是一个很酷的库,可以让您的应用程序命令行成为特色!

Update for year 2020!
Click library has easier usage
Fire is a cool library for making your app command line featured!

木有鱼丸 2024-12-28 06:53:10

迟到了,但我不得不做很多这样的事情,并且发现 argparse 做起来非常笨拙。这促使我编写了一个名为 arghandler 的 argparse 扩展,它具有明确的支持为此,可以用基本上零行代码来实现子命令。

这是一个例子:

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

Late to the party here, but I've had to do this quite a bit and have found argparse pretty clunky to do this with. This motivated me to write an extension to argparse called arghandler, which has explicit support for this - making is possible implement subcommands with basically zero lines of code.

Here's an example:

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()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文