天天看點

Python 指令行之旅:深入 argparse(二)Python 指令行之旅:深入 argparse(二)

Python 指令行之旅:深入 argparse(二)

原文發表于

Prodesire 部落格

前言

在上一篇“深入 argparse(一)”的文章中,我們深入了解了

argparse

的包括參數動作和參數類别在内的基本功能,具備了編寫一個簡單指令行程式的能力。本文将繼續深入了解

argparse

的進階玩法,一窺探其全貌,助力我們擁有實作複雜指令行程式的能力。

本系列文章預設使用 Python 3 作為解釋器進行講解。
若你仍在使用 Python 2,請注意兩者之間文法和庫的使用差異哦~           

幫助

自動生成幫助

當你在指令行程式中指定

-h

--help

參數時,都會輸出幫助資訊。而

argparse

可通過指定

add_help

入參為

True

或不指定,以達到自動輸出幫助資訊的目的。

>>> 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 兩個幫助内容上的差異:

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

來滿足:

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

分組中。

選項參數字首

不知你是否注意到,在不同平台上指令行程式的選項參數字首可能是不同的。比如在 Unix 上,其字首是

-

;而在 Windows 上,大多數指令行程式(比如

findstr

)的選項參數字首是

/

argparse

中,選項參數字首預設采用 Unix 指令行約定,也就是

-

。但它也支援自定義字首,下面是一個例子:

>>> 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 進行操作,兩類操作都需要指定

AccessKeyId

AccessKeySecret

來表明使用者身份和權限。那麼共享解析器就顯得尤為必要,這樣就可以少去重複代碼。

我們可以這樣做,在

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.py

aws.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.py

aws.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個子指令,由此即可實作整個功能。

在下面這個例子中,我們支援

create

delete

兩個子指令,用來建立或删除指定路徑。而

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_argument

action

入參。當解析器解析參數時,會調用該類的

__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

指令,想想是不是有些興奮呢?