Argparse 教學

作者:

Tshepang Mbambo

本教學旨在簡要介紹 argparse 這個 Python 標準函式庫中推薦的命令列剖析模組。

備註

標準函式庫包含另外兩個與命令列參數處理直接相關的函式庫:較低階的 optparse 模組(可能需要更多程式碼來為給定應用程式設定,但也允許應用程式要求 argparse 不支援的行為),以及非常低階的 getopt(專門用作 C 程式設計師可用的 getopt() 函式系列的等價)。雖然這個指南並未直接涵蓋這些模組,但 argparse 的許多核心概念最初來自於 optparse,因此本教學的某些部分也適用於 optparse 使用者。

概念

讓我們透過使用 ls 指令來展示我們將在本介紹教學中探索的功能類型:

$ ls
cpython  devguide  prog.py  pypy  rm-unused-function.patch
$ ls pypy
ctypes_configure  demo  dotviewer  include  lib_pypy  lib-python ...
$ ls -l
total 20
drwxr-xr-x 19 wena wena 4096 Feb 18 18:51 cpython
drwxr-xr-x  4 wena wena 4096 Feb  8 12:04 devguide
-rwxr-xr-x  1 wena wena  535 Feb 19 00:05 prog.py
drwxr-xr-x 14 wena wena 4096 Feb  7 00:59 pypy
-rw-r--r--  1 wena wena  741 Feb 18 01:01 rm-unused-function.patch
$ ls --help
Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
...

我們可以從這四個命令中學到一些概念:

  • ls 命令即便在沒有任何選項的情況下執行仍非常有用。它預設顯示目前目錄的內容。

  • 如果我們想要看到比它預設提供更多的內容,我們也需要多告訴它一點。在本例中,我們希望它顯示不同的目錄 pypy,我們做的是指定所謂的位置引數。之所以如此命名是因為程式應該只根據該值在命令列中出現的位置來知道如何處理該值。這個概念與 cp 這樣的指令更相關,其最基本的用法是 cp SRC DEST。第一個是你想要複製的位置,第二個是你想要複製過去的位置

  • 現在假設我們想要改變程式的行為。在我們的範例中,我們顯示每個檔案的更多資訊,而不僅是顯示檔案名稱。在這種情況下,-l 被稱為可選引數。

  • 這是幫助文字的片段。它非常有用,因為當你遇到以前從未使用過的程式時,只需閱讀其幫助文字即可了解它的工作原理。

基本用法

讓我們從一個非常簡單的例子開始,它(幾乎)什麼都不做:

import argparse
parser = argparse.ArgumentParser()
parser.parse_args()

程式碼執行結果如下:

$ python prog.py
$ python prog.py --help
usage: prog.py [-h]

options:
  -h, --help  show this help message and exit
$ python prog.py --verbose
usage: prog.py [-h]
prog.py: error: unrecognized arguments: --verbose
$ python prog.py foo
usage: prog.py [-h]
prog.py: error: unrecognized arguments: foo

這是發生的事情:

  • 執行不帶任何選項的腳本不會在標準輸出中顯示任何內容。不太有用。

  • 第二個開始能夠顯現 argparse 模組的有用之處。我們幾乎什麼也沒做,但我們已經收到了一個很好的幫助訊息。

  • --help 選項也可以縮寫為 -h,是我們能隨意獲得的唯一選項(即無需指定它)。指定任何其他內容都會導致錯誤。但即便如此,我們也還是輕鬆地獲得了有用的使用資訊。

位置引數的介紹

例如:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo")
args = parser.parse_args()
print(args.echo)

執行這段程式碼:

$ python prog.py
usage: prog.py [-h] echo
prog.py: error: the following arguments are required: echo
$ python prog.py --help
usage: prog.py [-h] echo

positional arguments:
  echo

options:
  -h, --help  show this help message and exit
$ python prog.py foo
foo

這是會發生的事情:

  • 我們新增了 add_argument() 方法,我們用它來指定程式願意接受哪些命令列選項。在本例中,我將其命名為 echo,以便與其功能一致。

  • 現在呼叫我們的程式時需要指定一個選項。

  • parse_args() 方法實際上從指定的選項中回傳一些資料,在本例中為 echo

  • 該變數是某種形式的「魔法」,argparse 可以自由執行(即無需指定該值儲存在哪個變數中)。你還會注意到,它的名稱與提供給方法 echo 的字串引數相符。

但請注意,儘管幫助顯示看起來不錯,但它目前還沒有發揮出應有的用處。例如,我們看到 echo 作為位置引數,但除了猜測或閱讀原始程式碼之外,我們不知道它的作用。那麼,我們來讓它變得更有用一點:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo", help="echo the string you use here")
args = parser.parse_args()
print(args.echo)

然後我們得到:

$ python prog.py -h
usage: prog.py [-h] echo

positional arguments:
  echo        echo the string you use here

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

現在來做一些更有用處的事情:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="display a square of a given number")
args = parser.parse_args()
print(args.square**2)

程式碼執行結果如下:

$ python prog.py 4
Traceback (most recent call last):
  File "prog.py", line 5, in <module>
    print(args.square**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

進展不太順利。這是因為,除非我們另有說明,argparse 會將我們給它的選項視為字串。因此,讓我們告訴 argparse 將該輸入視為整數:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="display a square of a given number",
                    type=int)
args = parser.parse_args()
print(args.square**2)

程式碼執行結果如下:

$ python prog.py 4
16
$ python prog.py four
usage: prog.py [-h] square
prog.py: error: argument square: invalid int value: 'four'

順利進展。現在該程式甚至可以在繼續操作之前因錯誤的非法輸入而退出。

可選引數的介紹

到目前為止,我們一直在討論位置引數。我們來看看如何新增可選引數:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbosity", help="increase output verbosity")
args = parser.parse_args()
if args.verbosity:
    print("verbosity turned on")

接者是結果:

$ python prog.py --verbosity 1
verbosity turned on
$ python prog.py
$ python prog.py --help
usage: prog.py [-h] [--verbosity VERBOSITY]

options:
  -h, --help            show this help message and exit
  --verbosity VERBOSITY
                        increase output verbosity
$ python prog.py --verbosity
usage: prog.py [-h] [--verbosity VERBOSITY]
prog.py: error: argument --verbosity: expected one argument

這是發生的事情:

  • 程式被編寫為在指定 --verbosity 時顯示一些內容,並在未指定時不顯示任何內容。

  • 為了表示該選項實際上是可選的,沒使用它來執行程式並不會出現錯誤。請注意,預設情況下,如果未使用可選引數,則相關變數(在本例中為 args.verbosity)將被賦予 None 作為值,這就是它未能通過 if 陳述式真值測試的原因。

  • 幫助訊息有點不同。

  • 當使用 --verbosity 選項時必須要指定一些值,任何值都可以。

在上面的例子中,--verbosity 接受任意的整數,但對我們的程式來說只接受兩個輸入值, TrueFalse。所以我們來修改一下程式碼使其符合:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", help="increase output verbosity",
                    action="store_true")
args = parser.parse_args()
if args.verbose:
    print("verbosity turned on")

接者是結果:

$ python prog.py --verbose
verbosity turned on
$ python prog.py --verbose 1
usage: prog.py [-h] [--verbose]
prog.py: error: unrecognized arguments: 1
$ python prog.py --help
usage: prog.py [-h] [--verbose]

options:
  -h, --help  show this help message and exit
  --verbose   increase output verbosity

這是發生的事情:

  • 這個選項現在更像是一個旗標,而不是需要值的東西。我們甚至更改了選項的名稱以符合這個想法。請注意,我們現在指定一個新的關鍵字 action,並為其指定值 "store_true"。這意味著,如果指定了該選項,則將值 True 指派給 args.verbose。不指定它代表為 False

  • 當你指定一個值時,它會本著旗標的實際精神來抱怨。

  • 請注意不同的幫助文字。

短選項

如果你熟悉命令列用法,你會注意到我尚未提及選項的簡短版本。這很簡單:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", help="increase output verbosity",
                    action="store_true")
args = parser.parse_args()
if args.verbose:
    print("verbosity turned on")

而這為:

$ python prog.py -v
verbosity turned on
$ python prog.py --help
usage: prog.py [-h] [-v]

options:
  -h, --help     show this help message and exit
  -v, --verbose  increase output verbosity

請注意,新功能也反映在幫助文字中。

組合位置引數和可選引數

我們的程式的複雜性不斷增加:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbose", action="store_true",
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbose:
    print(f"the square of {args.square} equals {answer}")
else:
    print(answer)

然後現在的輸出結果:

$ python prog.py
usage: prog.py [-h] [-v] square
prog.py: error: the following arguments are required: square
$ python prog.py 4
16
$ python prog.py 4 --verbose
the square of 4 equals 16
$ python prog.py --verbose 4
the square of 4 equals 16
  • 我們帶回了位置引數,因而被抱怨。

  • 請注意,順序並不重要。

我們讓這個程式擁有多個訊息詳細級別 (verbosity) 之值的能力,並實際使用它們:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbosity", type=int,
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
    print(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
    print(f"{args.square}^2 == {answer}")
else:
    print(answer)

接者是結果:

$ python prog.py 4
16
$ python prog.py 4 -v
usage: prog.py [-h] [-v VERBOSITY] square
prog.py: error: argument -v/--verbosity: expected one argument
$ python prog.py 4 -v 1
4^2 == 16
$ python prog.py 4 -v 2
the square of 4 equals 16
$ python prog.py 4 -v 3
16

除了最後一個外都看起來正常,它透露了我們程式中的一個錯誤。我們可透過限制 --verbosity 選項可以接受的值來修復它:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
    print(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
    print(f"{args.square}^2 == {answer}")
else:
    print(answer)

接者是結果:

$ python prog.py 4 -v 3
usage: prog.py [-h] [-v {0,1,2}] square
prog.py: error: argument -v/--verbosity: invalid choice: 3 (choose from 0, 1, 2)
$ python prog.py 4 -h
usage: prog.py [-h] [-v {0,1,2}] square

positional arguments:
  square                display a square of a given number

options:
  -h, --help            show this help message and exit
  -v, --verbosity {0,1,2}
                        increase output verbosity

請注意,更改也會反映在錯誤訊息和幫助字串中。

現在,讓我們使用另一種常見方法來玩玩訊息詳細級別。它也與 CPython 執行檔處理其自身訊息詳細級別引數的方式相符(請見 python --help 的輸出):

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display the square of a given number")
parser.add_argument("-v", "--verbosity", action="count",
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
    print(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
    print(f"{args.square}^2 == {answer}")
else:
    print(answer)

我們已經介紹過另一個操作 "count" 用來計算指定的選項出現的次數。

$ python prog.py 4
16
$ python prog.py 4 -v
4^2 == 16
$ python prog.py 4 -vv
the square of 4 equals 16
$ python prog.py 4 --verbosity --verbosity
the square of 4 equals 16
$ python prog.py 4 -v 1
usage: prog.py [-h] [-v] square
prog.py: error: unrecognized arguments: 1
$ python prog.py 4 -h
usage: prog.py [-h] [-v] square

positional arguments:
  square           display a square of a given number

options:
  -h, --help       show this help message and exit
  -v, --verbosity  increase output verbosity
$ python prog.py 4 -vvv
16
  • 是的,現在它更像是我們上一版腳本中的旗標(類似於 action="store_true"),這應該可以解釋抱怨的原因。

  • 它的行為也類似 "store_true" 操作。

  • 現在這裡示範了 "count" 動作的作用。你可能以前見過這種用法。

  • 如果你不指定 -v 旗標,則該旗標被視為具有 None 值。

  • 正如預期的那樣,指定長形式旗標,我們應該得到相同的輸出。

  • 遺憾的是,我們的幫助輸出對於我們腳本獲得的新功能並沒有提供太多資訊,但我們都可以透過改進腳本的文件來解決這個問題(例如:透過 help 關鍵字引數)。

  • 最後的輸出透露了我們程式中的一個錯誤。

讓我們來解決問題:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type