使用 argparse 為 Python 腳本加入 CUI 參數

如果說 GUI(Graphical User Interface,圖形化使用者介面)是給人們使用的介面,那 CUI (Character User Interface,字元使用者介面)就是給機器人跑自動化程式所使用的介面(嗯 Linux 使用者表示我們也都在用 CUI),總之是指一個在終端機裡僅靠輸入文字操作的介面,讓程式藉由傳入參數來完善功能。

本文用詞說明

因為命令列的參數選項跟 Python 方法(Method)的選項參數中文命名很混淆視聽(其實英文也都叫做 Argument),所以本文後面將統一用詞以區分彼此的不同,針對以下這一行命令列:

$ python sample.py --foo FOO

--foo 為「參數」,FOO 為「項目」,而 Python 中的參數語法叫「命名參數」。

基本用法

剖析真實的程式來思考如何用 argparse 實現參數行為。

以 rm 為例

以下示範寫一個 rm_arg.py 實現 rm 的參數行為:

from argparse import ArgumentParser

parser = ArgumentParser()
parser.add_argument("files", nargs="+", metavar="FILE")
parser.add_argument("-f", "--force", action="store_true", help="ignore ...")
parser.add_argument("-i", action="store_true", help="prompt before ...")
parser.add_argument("-I", action="store_true", help="Prompt once ...")
parser.add_argument("--interactive", metavar="WHEN", default="never", choices=["never", "once", "always"], help="Prompt according to WHEN ...")
parser.add_argument("--one-file-system", action="store_true", help="When removing a hierarchy recursively ...")
parser.add_argument("--no-preserve-root", action="store_true", help="Do not treat ...")
parser.add_argument("--preserve-root", action="store_true", help="Do not remove ...")
parser.add_argument("-r", "-R", "--recursive", action="store_true", help="Remove directories ...")
parser.add_argument("-d", "--dir", action="store_true", help="Remove empty directories ...")
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode ...")
parser.add_argument('--version', action='version', version='%(prog)s 1.0', help="Display version ...")

args = parser.parse_args()

最後一行一定要寫才會去解析系統參數,可以在終端機輸入 python rm_arg.py --help 以查看 Help 文字的效果。

add_argument 的命名參數説明:

  • default 設定參數不存在時的預設值,不寫的話其值為 None。
  • nargs 指定此參數需要的項目個數,不寫的話為 1,常用以下方式指定:
    • N 一個整數數字(預設行為)
    • "?" 0個或1個
    • "*" 0個或多個
    • "+" 1個或多個
  • choices 指定可用項目,不寫表示無限制。
  • metavar 自定 Help 文字裡此參數的項目占位符(Placeholder),不寫會用大寫的參數名稱。
    • nargsN 時建議使用同長度的 Tuple,例如 metavar=("x", "y", "z")
    • 其他的情況指定一個喜歡的字串即可
  • action 指定參數存在時的特別行為,常用以下值:
    • "store_true" 參數存在時為 True,預設值為 False
    • "append" 表示此參數可指定複數次,以 List 型別儲存。留意:無指定 default 也無設定此參數的情況下會取得 None 值。
  • required 設定為 True 表示此參數為必須。

取得參數值

使用 . (Dot Notation) 取值,注意參數名中的 - (Dash) 會自動轉成底線以符合 Python 的變數命名規則,若想更換屬性的命名可以使用 dest 命名參數(詳見下方的 shutdown 範例說明)。

print(args.files)
print(args.one_file_system)

也可以藉由 vars 函式來歷遍所有可用 Attribute。

for key, value in vars(args).items():
    print(key, value)

進階用法

在創建 ArgumentParser 時可以藉由帶入額外命名參數來進一步客製化 argparse 行為。

加入互斥的選項

有時多個參數為互相排斥,也就是多個參數只能擇一,可以利用 add_mutually_exclusive_group 來達成:

>>> parser = ArgumentParser()
>>> group = parser.add_mutually_exclusive_group(required=True)
>>> group.add_argument("--max", action="store_true")
>>> group.add_argument("--min", action="store_true")
>>> parser.print_help()
usage: min_max.py [-h] (--max | --min)

optional arguments:
  -h, --help  show this help message and exit
  --max
  --min

add_mutually_exclusive_group 的命名參數 required 預設值為 False,也就是要嘛不寫要嘛也只能寫一個的意思。

自定 Help 文字

帶入 progusagedescriptionepilog 等命名參數來進一步客製化 Help 文字訊息。

>>> parser = ArgumentParser(
        prog="PROG_NAME",
        usage="%(prog)s [OPTIONS]",
        description="DESCRIPTION SECTION",
        epilog="EPILOG SECTION")
>>> parser.print_help()
usage: PROG_NAME [OPTIONS]

DESCRIPTION SECTION

options:
 -h, --help  show this help message and exit

EPILOG SECTION

為參數說明列表分群組

可以使用 add_argument_group來在 Help 文字中分群組說明不同參數的用途,對參數的功能沒有實質上的影響,只是排版好看。使用方法很簡單,就是在原有的 parser 呼叫 add_argument_group 方法創建群組後把參數加在群組裡即可,可以參見下方的 shutdown 範例。

改變參數的前綴字符

若想使用 - (dash) 以外的字符來標記為參數,可以使用 prefix_chars 命名參數,例如 Windows 平台很喜歡用 / (slash):

>>> parser = ArgumentParser(prefix_chars='/')
>>> parser.print_help()
usage: sample.py [/h]

optional arguments:
  /h, //help  show this help message and exit

其他用法

留意本段落的範例寫法:

>>> parser.parse_args(['-i', '-foo', 'BOO'])

等同於去解析終端機中的以下輸入:

$ python sample.py -i -foo BOO

強制完整參數名稱

argparse 的預設行為允許使用任意長度的參數名稱縮寫(若不與其他參數名稱衝突)。

>>> parser = ArgumentParser()
>>> parser.add_argument("--context")
>>>
>>> parser.parse_args(["--c", "123"])
Namespace(context="123")
>>> parser.parse_args(["--con", "123"])
Namespace(context="123")

命名參數中帶入 allow_abbrev=False 可以禁止自動辨識縮寫:

>>> parser = ArgumentParser(allow_abbrev=False)
>>> parser.add_argument("--context")
>>>
>>> parser.parse_args(["--c", "123"])
error: unrecognized arguments: --c 123
An exception has occurred, use %tb to see the full traceback.
更詳細的用法可以參考官網完整用法列表

以 shutdown 為例

最後示範寫一個 shutdown_arg.py 實現 Windows 平台 shutdown 的參數行為:

from argparse import ArgumentParser

p = ArgumentParser(
    add_help=False,
    allow_abbrev=False,
    prefix_chars='/',
    description="可讓您一次關閉或重新開機本機或遠端電腦。",
    usage="%(prog)s [/i | /l | /s | /sg | /r | /g | /a | /p | /h | /e | /o] [/hybrid] [/fw] [/f] [/m \\computer][/t xxx][/d [p|u:]xx:yy [/c 'comment']]",
    epilog="備註:使用者必須獲指派關閉系統使用者權限...",
)

action_group = p.add_mutually_exclusive_group(required=False)
action_group.add_argument("/i", action="store_const", const="i", dest="action", help="顯示 [遠端關機 ] 方塊 ...")
action_group.add_argument("/l", action="store_const", const="l", dest="action", help="立即登出目前的使用者 ...")
# ...
action_group.add_argument("/h", action="store_const", const="h", dest="action", help="如果已啟用休眠,請將本機電腦放入休眠狀態 ...")
# ...
action_group.add_argument("/o", action="store_const", const="o", dest="action", help="移至 [ 進階開機選項] 功能表並重新啟動裝置 ...")

option_group = p.add_argument_group(title="other options")
option_group.add_argument("/hybrid", action="store_true", help="關閉裝置並準備快速啟動 ...")
option_group.add_argument("/f", action="store_true", help="強制執行應用程式關閉而不警告使用者。")
option_group.add_argument("/m", metavar="\\\\", help="指定目的電腦。 無法搭配 /l 選項使用。")
option_group.add_argument("/t", default=30, type=int, metavar="", help="將關機之前的逾時期間設定為 xxx 秒 ...")

comment_group = p.add_argument_group(title="add comment")
comment_group.add_argument("/d", metavar="[p | u:]:", action="append", dest="comment", help="列出系統重新開機或關機的原因 ...")
comment_group.add_argument("/c", metavar="", action="append", dest="comment", help="可以讓您詳細註解關機的原因 ...")

p.add_argument("/help", action="help", help="在命令提示字元中顯示說明 ...")

args = p.parse_args()

if args.comment:
    comment_parser = ArgumentParser(prefix_chars='/')
    comment_parser.add_argument("reason_code")
    comment_parser.add_argument("detail", nargs="?", default="")
    comment_args = comment_parser.parse_args(args.comment)

眼尖的同學們會發現此例子中有使用到 -h 參數,也就是預設顯示 Help 的參數,可以在創建 ArgumentParser 時帶入 add_help=False 以取消預設的 /h//help 參數,然後再自己加入一個指定 action="help" 的參數來保留 Help 功能。

add_argument 的命名參數説明:

  • type 可以將參數項目轉為指定型別,預設為 str
  • action 指定參數存在時的特別行為:
    • "store_const" 參數存在時為使用 const 命名參數指定的值,否則使用 default
    • "append" 表示此參數的項目值將會 Append 至 List 列表
      • 若存在 dest 命名參數,存至其指定的命名屬性,預設存至以參數命名的屬性
  • dest 將參數的項目設值寫入此 Attribute 名稱中
    • /d 為例,參數選項 /d 111 將以 args.comment = ["111"] 方式存取

另外還新增了另一個 comment_parser 專門負責解析 Comment 字串,可以藉由以下程式碼查看屬性值。

if args.comment:
    #...
    print(comment_args.reason_code)
    print(comment_args.detail)

 

References