Python cmd2:参数处理详解
参数处理
cmd2 使得使用 Python 的 argparse 模块为命令添加复杂的参数处理变得非常简单。cmd2 会为你处理以下事项:
- 以类似 POSIX shell 的方式解析输入和带引号的字符串
- 使用你提供的 Cmd2ArgumentParser 实例解析参数列表
- 将解析后的
argparse.Namespace对象传递给你的命令函数。Namespace中包含解析命令行时创建的cmd2.Statement对象,可通过Namespace的cmd2_statement属性访问 - 将参数解析器的用法信息添加到命令的帮助文档中
- 检查是否存在
-h/--help选项,如果存在则显示命令的帮助信息
这些功能都由从 cmd2 导入的 @with_argparser 装饰器提供。
参阅 argparse_example 示例,了解更多关于如何在 cmd2 应用中使用各种参数处理装饰器的信息。
cmd2 提供了以下装饰器来辅助解析传递给命令的参数:
cmd2.decorators.with_argparsercmd2.decorators.with_argument_list
所有这些装饰器都接受一个可选的 preserve_quotes 参数,默认值为 False。将此参数设为 True 在你需要将参数传递给另一个可能有自己参数解析逻辑的命令时非常有用。
with_argparser 装饰器
@with_argparser 装饰器的第一个参数可以接受以下类型:
- 一个现有的
Cmd2ArgumentParser实例 - 一个返回
Cmd2ArgumentParser实例的函数或静态方法 - 一个返回
Cmd2ArgumentParser实例的 Cmd 或 CommandSet 类方法
在所有情况下,@with_argparser 装饰器都会创建解析器实例的深拷贝并存储在内部。这意味着解析器不需要在不同命令之间保持唯一。
由于 @with_argparser 装饰器会对提供的解析器进行深拷贝,如果你希望稍后动态修改该解析器,你需要获取这个深拷贝。可以通过 self.command_parsers.get(self.do_commandname) 来实现。
参数解析
对于 cmd2.Cmd 子类中每个需要参数解析的命令,创建一个 Cmd2ArgumentParser 实例来适当地解析该命令的输入(或提供一个返回此类解析器的函数/方法)。然后用 @with_argparser 装饰器装饰命令方法,将参数解析器作为装饰器的第一个参数传递。这会改变命令方法的第二个参数,该参数将包含 Cmd2ArgumentParser.parse_args() 的结果。
以下是具体示例:
from cmd2 import Cmd2ArgumentParser, with_argparser
argparser = Cmd2ArgumentParser()argparser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')argparser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')argparser.add_argument('-r', '--repeat', type=int, help='output [n] times')argparser.add_argument('word', nargs='?', help='word to say')
@with_argparser(argparser)def do_speak(self, opts): """Repeats what you tell me to.""" arg = opts.word if opts.piglatin: arg = '%s%say' % (arg[1:], arg[0]) if opts.shout: arg = arg.upper() repetitions = opts.repeat or 1 for i in range(min(repetitions, self.maxrepeats)): self.poutput(arg)cmd2 会根据被装饰方法的名称设置参数解析器中的 prog 变量。这会覆盖你在创建参数解析器时指定的任何 prog 值。
从 3.0.0 版本开始,cmd2 在创建实例特定的解析器时设置 prog,这比之前的版本要晚。
帮助消息
默认情况下,当用户请求某个命令的帮助时,cmd2 使用命令方法的文档字符串。当你使用 @with_argparser 装饰器时,do_* 方法的文档字符串会被用来设置 Cmd2ArgumentParser 的 description。
虽然 help 字段只是一个简单的字符串,但 description 和 epilog 字段可以接受任何 rich 可渲染对象。这允许你使用 rich 的所有内置对象,如 Text、Table 和 Markdown。
使用以下代码:
from cmd2 import Cmd2ArgumentParser, with_argparser
argparser = Cmd2ArgumentParser()argparser.add_argument('tag', help='tag')argparser.add_argument('content', nargs='+', help='content to surround with tag')@with_argparser(argparser)def do_tag(self, args): """Create an HTML tag""" self.stdout.write('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content))) self.stdout.write('\n')help tag 命令将显示:
usage: tag [-h] tag content [content ...]
Create an HTML tag
positional arguments: tag tag content content to surround with tag
optional arguments: -h, --help show this help message and exit如果你愿意,可以在实例化 Cmd2ArgumentParser 时设置 description,并留空方法的文档字符串:
from cmd2 import Cmd2ArgumentParser, with_argparser
argparser = Cmd2ArgumentParser(description='create an HTML tag')argparser.add_argument('tag', help='tag')argparser.add_argument('content', nargs='+', help='content to surround with tag')@with_argparser(argparser)def do_tag(self, args): self.stdout.write('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content))) self.stdout.write('\n')现在当用户输入 help tag 时会看到:
usage: tag [-h] tag content [content ...]
create an HTML tag
positional arguments: tag tag content content to surround with tag
optional arguments: -h, --help show this help message and exit要在生成的帮助消息末尾添加额外文本,可以使用 epilog 变量:
from cmd2 import Cmd2ArgumentParser, with_argparser
argparser = Cmd2ArgumentParser(description='create an HTML tag', epilog='This command cannot generate tags with no content, like <br/>.')argparser.add_argument('tag', help='tag')argparser.add_argument('content', nargs='+', help='content to surround with tag')@with_argparser(argparser)def do_tag(self, args): self.stdout.write('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content))) self.stdout.write('\n')结果如下:
usage: tag [-h] tag content [content ...]
create an HTML tag
positional arguments: tag tag content content to surround with tag
optional arguments: -h, --help show this help message and exit
This command cannot generate tags with no content, like <br/>如果命令 foo 使用了 cmd2 的 with_argparse 装饰器,那么当调用 help foo 时,help_foo 将不会被调用。argparse 模块提供了一个丰富的 API,可以用来调整显示帮助的各个方面,我们鼓励 cmd2 开发者利用它。
Argparse HelpFormatter 类
cmd2 提供了 5 种不同的 Argparse HelpFormatter 类,它们都基于 rich-argparse 的 RichHelpFormatter 类。好处是你的 cmd2 应用现在拥有了更美观的帮助信息,包含颜色使其更容易快速阅读。这对所有支持的 Python 版本都适用。
Cmd2HelpFormatter- 默认的帮助格式化类ArgumentDefaultsCmd2HelpFormatter- 在参数帮助中添加默认值MetavarTypeCmd2HelpFormatter- 使用参数的type作为默认的 metavar 值(而非参数的dest)RawDescriptionCmd2HelpFormatter- 保留 description 和 epilog 中的格式RawTextCmd2HelpFormatter- 保留所有帮助文本的格式
默认的 Cmd2HelpFormatter 类继承自 argparse.HelpFormatter。如果你需要不同的行为,请将所需的类传递给 argparse 解析器的 formatter_class 参数,例如 formatter_class=ArgumentDefaultsCmd2HelpFormatter。
参数列表
cmd2 的默认行为是将用户输入直接作为字符串传递给你的 do_* 方法。传递给方法的对象实际上是一个 cmd2.Statement 对象,它具有一些可能有用的额外属性,包括 arg_list 和 argv:
class CmdLineApp(cmd2.Cmd): """ Example cmd2 application. """
def do_say(self, statement): # statement contains a string self.poutput(statement)
def do_speak(self, statement): # statement also has a list of arguments # quoted arguments remain quoted for arg in statement.arg_list: self.poutput(arg)
def do_articulate(self, statement): # statement.argv contains the command # and the arguments, which have had quotes # stripped for arg in statement.argv: self.poutput(arg)如果你不想访问传递给 do_* 方法的字符串上的额外属性,你仍然可以让 cmd2 对用户输入应用 shell 解析规则,并传递参数列表而非字符串。只需在那些应接收参数列表而非字符串的方法上应用 @with_argument_list 装饰器:
from cmd2 import with_argument_list
class CmdLineApp(cmd2.Cmd): """ Example cmd2 application. """
def do_say(self, cmdline): # cmdline contains a string pass
@with_argument_list def do_speak(self, arglist): # arglist contains a list of arguments pass未知位置参数
要将所有未知参数作为字符串列表传递给你的命令,请使用 @with_argparser(..., with_unknown_args=True) 装饰器来装饰命令方法。
以下是具体示例:
from cmd2 import Cmd2ArgumentParser, with_argparser
dir_parser = Cmd2ArgumentParser()dir_parser.add_argument('-l', '--long', action='store_true', help="display in long format with one item per line")
@with_argparser(dir_parser, with_unknown_args=True)def do_dir(self, args, unknown): """List contents of current directory.""" # No arguments for this command if unknown: self.perror("dir does not take any positional arguments:") self.do_help('dir') self.last_result = 'Bad arguments' return
# Get the contents as a list contents = os.listdir(self.cwd)
...使用自定义命名空间
在某些情况下,可能需要编写依赖于应用程序状态数据的自定义 argparse 代码。为了在仍然允许使用装饰器的同时支持这种能力,@with_argparser 有一个名为 ns_provider 的可选参数。
ns_provider 是一个接受 cmd2.Cmd 对象作为参数并返回 argparse.Namespace 的可调用对象:
Callable[[cmd2.Cmd], argparse.Namespace]例如:
def settings_ns_provider(self) -> argparse.Namespace: """Populate an argparse Namespace with current settings""" ns = argparse.Namespace() ns.app_settings = self.settings return ns要将此函数与 @with_argparser 装饰器一起使用,按如下方式操作:
@with_argparser(my_parser, ns_provider=settings_ns_provider)命名空间由装饰器传递给 argparse 的解析函数,使你的自定义代码能够访问其解析逻辑所需的状态数据。
子命令
使用 @with_argparser 装饰器的命令支持子命令。语法基于 argparse 的 sub-parsers。
你可以为命令添加多层子命令。cmd2 会自动遍历和补全所有使用 argparse 的命令的子命令。
参阅 argparse_example 示例,了解更多关于如何在 cmd2 应用中使用子命令的信息。
@as_subcommand_to 装饰器使得添加子命令变得简单。
Argparse 扩展
cmd2 用范围元组功能增强了标准的 argparse.nargs:
nargs=(5,)- 接受 5 个或更多项nargs=(8, 12)- 接受 8 到 12 项
cmd2 还提供了 Cmd2ArgumentParser 类,它继承自 argparse.ArgumentParser 并改进了错误和帮助输出。
装饰器顺序
如果你在 @cmd2.with_argparser 之外还使用了自定义装饰器,那么自定义装饰器相对于 cmd2 装饰器的顺序会影响运行时行为和 argparse 错误处理。这不是 cmd2 特有的,这只是 Python 中装饰器工作方式的一个副作用。要了解更多关于装饰器如何工作的信息,请参阅 decorator_primer。
如果你希望自定义装饰器的运行时行为在 argparse 错误时也能执行,则该装饰器需要放在 argparse 装饰器之后,例如:
@cmd2.with_argparser(foo_parser)@my_decoratordef do_foo(self, args: argparse.Namespace) -> None: """foo docs""" pass但如果你不希望自定义装饰器的运行时行为在 argparse 错误时执行,则该装饰器需要放在 argparse 装饰器之前,例如:
@my_decorator@cmd2.with_argparser(bar_parser)def do_bar(self, args: argparse.Namespace) -> None: """bar docs""" passhelp_categories 示例以具体的方式演示了以上两种情况。
保留的参数名称
cmd2 的 @with_argparser 装饰器会向 argparse Namespace 添加以下属性。为避免命名冲突,请不要将这些名称用于你的 argparse 参数。
cmd2_statement- 解析命令行时创建的cmd2.Statement对象cmd2_subcmd_handler- 子命令处理函数,如果未设置则为None
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!