Python 命令行之旅:深刻 argparse(二)

做者:HelloGitHub- Prodesire

前言

在上一篇“深刻 argparse(一)”的文章中,咱们深刻了解了 argparse 的包括参数动做和参数类别在内的基本功能,具有了编写一个简单命令行程序的能力。本文将继续深刻了解 argparse 的进阶玩法,一窥探其全貌,助力咱们拥有实现复杂命令行程序的能力。python

本系列文章默认使用 Python 3 做为解释器进行讲解。
若你仍在使用 Python 2,请注意二者之间语法和库的使用差别哦~
复制代码

帮助

自动生成帮助

当你在命令行程序中指定 -h--help 参数时,都会输出帮助信息。而 argparse 可经过指定 add_help 入参为 True 或不指定,以达到自动输出帮助信息的目的。git

>>> import argparse
>>> parser = argparse.ArgumentParser(add_help=True)
>>> parser.add_argument('--foo')
>>> parser.parse_args(['-h'])
usage: [-h] [--foo FOO]

optional arguments:
  -h, --help  show this help message and exit
  --foo FOO
复制代码

若是 add_help=False,那么在命令行中指定 -h 则会报错:编程

>>> import argparse
>>> parser = argparse.ArgumentParser(add_help=False)
>>> parser.add_argument('--foo')
>>> parser.parse_args(['-h'])
usage: [--foo FOO]
: error: unrecognized arguments: -h
复制代码

自定义帮助

ArgumentParser 使用 formatter_class 入参来控制所输出的帮助格式。 好比,经过指定 formatter_class=argparse.RawTextHelpFormatter,咱们可让帮助内容遵循原始格式:数组

>>> import argparse
>>> parser = argparse.ArgumentParser(
...     add_help=True,
...     formatter_class=argparse.RawTextHelpFormatter,
...     description=""" ... description ... raw ... formatted"""
... )
>>> parser.add_argument(
...     '-a', action="store_true",
...     help="""argument ... raw ... formatted ... """
... )
>>>
>>> parser.parse_args(['-h'])
usage: [-h] [-a]

    description
        raw
           formatted

optional arguments:
  -h, --help  show this help message and exit
  -a          argument
                      raw
                          formatted
复制代码

对比下不指定 formatter_class 的帮助输出,就能够发现 descirption 和 -a 两个帮助内容上的差别:bash

>>> import argparse
>>> parser = argparse.ArgumentParser(
...     add_help=True,
...     description=""" ... description ... notraw ... formatted"""
... )
>>> parser.add_argument(
...     '-a', action="store_true",
...     help="""argument ... notraw ... formatted ... """
... )
>>> parser.parse_args(['-h'])
usage: [-h] [-a]

description notraw formatted

optional arguments:
  -h, --help  show this help message and exit
  -a          argument notraw formatted
复制代码

参数组

有时候,咱们须要给参数分组,以使得在显示帮助信息时可以显示到一块儿。工具

好比某命令行支持三个参数选项 --user--password--push,前二者须要放在一个名为 authentication 的分组中以表示它们是身份认证信息。那么咱们能够用 ArgumentParser.add_argument_group 来知足:ui

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> group = parser.add_argument_group('authentication')
>>> group.add_argument('--user', action="store")
>>> group.add_argument('--password', action="store")
>>> parser.add_argument('--push', action='store')
>>> parser.parse_args(['-h'])
usage: [-h] [--user USER] [--password PASSWORD] [--push PUSH]

optional arguments:
  -h, --help           show this help message and exit
  --push PUSH

authentication:
  --user USER
  --password PASSWORD
复制代码

能够看到,当咱们输出帮助信息时,--user--password 选项都出如今 authentication 分组中。this

选项参数前缀

不知你是否注意到,在不一样平台上命令行程序的选项参数前缀多是不一样的。好比在 Unix 上,其前缀是 -;而在 Windows 上,大多数命令行程序(好比 findstr)的选项参数前缀是 /阿里云

argparse 中,选项参数前缀默认采用 Unix 命令行约定,也就是 -。但它也支持自定义前缀,下面是一个例子:spa

>>> import argparse
>>> 
>>> parser = argparse.ArgumentParser(
...     description='Option prefix',
...     prefix_chars='-+/',
... )
>>> 
>>> parser.add_argument('-power', action="store_false",
...                     default=None,
...                     help='Set power off',
...                     )
>>> parser.add_argument('+power', action="store_true",
...                     default=None,
...                     help='Set power on',
...                     )
>>> parser.add_argument('/win',
...                     action="store_true",
...                     default=False)
>>> parser.parse_args(['-power'])
Namespace(power=False, win=False)
>>> parser.parse_args(['+power', '/win'])
Namespace(power=True, win=True)
复制代码

在这个例子中,咱们指定了三个选项参数前缀 -+/,从而:

  • 经过指定选项参数 -power,使得 power=False
  • 经过指定选项参数 +power,使得 power=True
  • 经过指定选项参数 /win,使得 win=True

共享解析器

有些时候咱们须要共享解析器,以共享里面的参数配置。好比,咱们的命令行工具须要支持对阿里云和 AWS 进行操做,两类操做都须要指定 AccessKeyIdAccessKeySecret 来代表用户身份和权限。那么共享解析器就显得尤其必要,这样就能够少去重复代码。

咱们能够这样作,在 base.py 中定义一个父解析器,存放 AccessKey 相关参数配置,做为公用的解析器。因为后续的子解析器会自动生成帮助信息,这里的父解析器指定 add_help=False 以不自动生成帮助信息:

# bash.py
import argparse

parser = argparse.ArgumentParser(add_help=False)

parser.add_argument('--ak-id', action="store")
parser.add_argument('--ak-secret', action="store")
复制代码

而后就能够分别在 ali.pyaws.py 中分别定义子解析器,经过 parents 入参指定上述父解析器,从而继承公共的参数,并实现各自的参数:

# ali.py
import argparse
import base

parser = argparse.ArgumentParser(
    parents=[base.parser],
)

parser.add_argument('--ros',
                    action="store_true",
                    default=False,
                    help='Using ROS service to orchestrate cloud resources')

print(parser.parse_args())
复制代码
# aws.py
import argparse
import base

parser = argparse.ArgumentParser(
    parents=[base.parser],
)

parser.add_argument('--cloudformation',
                    action="store_true",
                    default=False,
                    help='Using CloudFormation service to orchestrate cloud resources')

print(parser.parse_args())
复制代码

最终经过 -h 参数分别看 ali.pyaws.py 所支持的参数,其中共同参数为 --ak-id--ak-secret,特定参数分别为 --ros--cloudformation

$ python3 ali.py -h

usage: ali.py [-h] [--ak-id AK_ID] [--ak-secret AK_SECRET] [--ros]

optional arguments:
  -h, --help            show this help message and exit
  --ak-id AK_ID
  --ak-secret AK_SECRET
  --ros                 Using ROS service to orchestrate cloud resources
复制代码
$ python3 aws.py -h

usage: aws.py [-h] [--ak-id AK_ID] [--ak-secret AK_SECRET] [--cloudformation]

optional arguments:
  -h, --help            show this help message and exit
  --ak-id AK_ID
  --ak-secret AK_SECRET
  --cloudformation      Using CloudFormation service to orchestrate cloud
                        resources
复制代码

嵌套解析器

咱们以前介绍的命令行中,使用形式一般是 cli --a --b xxx。但还有一种极为常见的命令行使用方式是 cli subcmd --a --b xxx。好比当咱们要经过 git 推送标签时,会用到 git push --tags

经过实现嵌套解析器,咱们能够很容易地对这种子命令的形式进行解析。

在嵌套解析器中,咱们定义一个父解析器来做为整个命令行的入口,再分别定义N个子解析器来对应N个子命令,由此便可实现整个功能。

在下面这个例子中,咱们支持 createdelete 两个子命令,用来建立或删除指定路径。而 delete 命令支持 --recursive 参数来代表是否递归删除指定路径:

# cli.py
import argparse

parser = argparse.ArgumentParser()

subparsers = parser.add_subparsers(help='commands')

# Create
create_parser = subparsers.add_parser(
    'create', help='Create a directory')
create_parser.add_argument(
    'dirname', action='store',
    help='New directory to create')

# Delete
delete_parser = subparsers.add_parser(
    'delete', help='Remove a directory')
delete_parser.add_argument(
    'dirname', action='store', help='The directory to remove')
delete_parser.add_argument(
    '--recursive', '-r', default=False, action='store_true',
    help='Recursively remove the directory',
)

print(parser.parse_args())
复制代码

直接指定 -h 来查看所支持的子命令和参数选项:

$ python3 cli.py -h

usage: cli.py [-h] {create,delete} ...

positional arguments:
  {create,delete}  commands
    create         Create a directory
    delete         Remove a directory

optional arguments:
  -h, --help       show this help message and exit
复制代码

直接指定 delete -h 来查看 delete 子命令支持的参数选项:

$ python3 cli.py delete -h

usage: cli.py delete [-h] [--recursive] dirname

positional arguments:
  dirname          The directory to remove

optional arguments:
  -h, --help       show this help message and exit
  --recursive, -r  Recursively remove the directory
复制代码

自定义动做

在上一篇“深刻 argparse (一)”的文章中介绍过8种参数动做,能够说是覆盖了绝大部分场景。可是也会有一些特定需求没法被知足,好比但愿获取到的参数值都是大写。在这种状况下,自定义动做就派上了用场。

实现一个自定义动做类,需继承自 argparse.Action,这个自定义动做类要传入到 ArgumentParser.add_argumentaction 入参。当解析器解析参数时,会调用该类的 __call__ 方法,该方法的签名为 __call__(self, parser, namespace, values, option_string=None),其中:

  • parser 为解析器实例
  • namespace 存放解析结果
  • values 即命令行中传入的参数值
  • option_string 为参数选项

在下面的例子中,咱们经过 --words 传入单词,并在自定义动做类中将其值转换为大写:

# cli.py
import argparse

class WordsAction(argparse.Action):

    def __call__(self, parser, namespace, values, option_string=None):
        print(f'parser = {parser}')
        print(f'values = {values!r}')
        print(f'option_string = {option_string!r}')

        values = [v.upper() for v in values]
        setattr(namespace, self.dest, values)


parser = argparse.ArgumentParser()
parser.add_argument('--words', nargs='*', action=WordsAction)

results = parser.parse_args()
print(results)
复制代码
$ python3 cli.py --words foo bar

parser = ArgumentParser(prog='cli.py', usage=None, description=None, formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)
values = ['foo', 'bar']
option_string = '--words'
Namespace(words=['FOO', 'BAR'])
复制代码

小节

经过对 argparse由浅入深的介绍,相信你已经全面了解了 argparse 的威力,也具有了开发命令行工具的能力。但“纸上得来终觉浅,绝知此事要躬行”。

在下篇文章中,将带你们一块儿用 argparse 实现平常工做中常见的 git 命令,想一想是否是有些兴奋呢?

欢迎关注 HelloGitHub 公众号,获取更多开源项目的资料和内容

『讲解开源项目系列』启动——让对开源项目感兴趣的人再也不畏惧、让开源项目的发起者再也不孤单。跟着咱们的文章,你会发现编程的乐趣、使用和发现参与开源项目如此简单。欢迎联系咱们给咱们投稿,让更多人爱上开源、贡献开源~

相关文章
相关标签/搜索