line
参数。 我喜欢argparse-之前我曾几次将其用于“预期”目的-因此我想尝试一下。不幸的是,由于许多设计决策都被强加给您(例如,在无效参数上调用
sys.exit()
等),因此它不太适合以“非常规”方式使用。我环顾四周寻找解决方案,并找到了这个。但是,该解决方案放弃了
cmd
的可用功能列表及其帮助功能。同样,它要求所有可用命令及其参数都放在一个位置(即__init__
),这在有许多命令时不切实际。我想出了使用装饰器的解决方案如下:
class WrapperCmdLineArgParser:
def __init__(self, parser):
"""Init decorator with an argparse parser to be used in parsing cmd-line options"""
self.parser = parser
self.help_msg = ""
def __call__(self, f):
"""Decorate 'f' to parse 'line' and pass options to decorated function"""
if not self.parser: # If no parser was passed to the decorator, get it from 'f'
self.parser = f(None, None, None, True)
def wrapped_f(*args):
line = args[1].split()
try:
parsed = self.parser.parse_args(line)
except SystemExit:
return
f(*args, parsed=parsed)
wrapped_f.__doc__ = self.__get_help(self.parser)
return wrapped_f
@staticmethod
def __get_help(parser):
"""Get and return help message from 'parser.print_help()'"""
f = tempfile.SpooledTemporaryFile(max_size=2048)
parser.print_help(file=f)
f.seek(0)
return f.read().rstrip()
它允许独立定义命令/功能,并引入第三个参数
parsed
,其中将包含成功的parse_args()
的返回值。定义函数/命令的方法有2种:这允许将解析器作为参数传递给装饰器。一种简单的方法是定义名称混乱的静态变量,如下
__test1_parser
所示。__test1_parser = argparse.ArgumentParser(prog="test1")
__test1_parser.add_argument('--foo', help="foo help")
@WrapperCmdLineArgParser(parser=__test1_parser)
def do_test1(self, line, parsed):
print("Test1...")
print(parsed)
在某些情况下,特别是如果解析器很复杂,则最好定义函数本身中的解析器。这也是可能的。该函数必须采用第4个参数
get_parser=False
,并在True
时返回解析器。@WrapperCmdLineArgParser(parser=None)
def do_test2(self, line, parsed, get_parser=False):
if get_parser:
parser = argparse.ArgumentParser(prog="test2")
parser.add_argument('--bar', help="bar help")
return parser
print("Test2...")
print(parsed)
无论定义它们的方式如何,函数都将列出在
cmd
的帮助下 (Cmd) help
Documented commands (type help <topic>):
========================================
help test1 test2
以及具有自己的帮助消息-通过
help <func_name>
或<func_name> -h
。 (Cmd) help test1
usage: test1 [-h] [--foo FOO]
optional arguments:
-h, --help show this help message and exit
--foo FOO foo help
(Cmd) test2 -h
usage: test2 [-h] [--bar BAR]
optional arguments:
-h, --help show this help message and exit
--bar BAR bar help
此外,当解析无效参数时,错误消息将被打印,并且将返回到提示,而无需调用给定的函数。
(Cmd) test1 --unk usage: test1 [-h] [--foo FOO] test1: error: unrecognized arguments: --unk (Cmd) 的命令,同时保持并且不与argparse
的任何功能冲突。
此解决方案可能有哪些缺点?有哪些可能的改进或明显的错误?使用cmd
和cmd
是否有更好的解决方案?是否存在提供我要实现的功能的其他模块?上面两种方法中的哪一种更好?
到目前为止,我已经注意到,在PyCharm的调试器控制台上,当存在无效参数并且引发argparse
时,错误消息和提示有时会重叠或不存在的顺序。它不会在常规终端上发生。
#1 楼
与其说是代码审查,不如说是架构建议。您的代码很好,但是我对当事情变得越来越复杂时倾向于增加的聪明之处感到厌倦。
除非您真的想从单个.py模块运行所有内容,否则我会建议将程序分成多个部分。将共享的片段放在一个公共库中,并通过调用外部实用程序将不同的组分成不同的可执行文件,就像git和apt do一样。这也使其他人可以轻松地为您的实用程序编写附件。
想想utility verb --be-awesome
就是在后台调用utility-verb --be-awesome
并传递它得到的任何命令行参数。作为一个有用的约定,您可能希望具有一些影响基本实用程序的参数,这些参数在调用时可能会从命令行中删除,或者只是强制将它们放在utility
和verb
部件之间。所有utility-verb
命令都应该能够依赖库上的共享功能。
评论
您看过点击吗?很好,我认为可以将用于测试的cliRunner转换为cmd样式的提示。@pjz我没有;确实看起来不错。我现在已经离开了那个项目,但是感谢您的指导!
如何将部分解析与子命令一起使用?