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 公衆號,獲取更多開源項目的資料和內容

『講解開源項目系列』啓動——讓對開源項目感興趣的人再也不畏懼、讓開源項目的發起者再也不孤單。跟着咱們的文章,你會發現編程的樂趣、使用和發現參與開源項目如此簡單。歡迎聯繫咱們給咱們投稿,讓更多人愛上開源、貢獻開源~

相關文章
相關標籤/搜索