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
這是正在發生的事情
在不帶任何選項的情況下執行指令碼會導致沒有任何內容顯示到 stdout。沒什麼用處。
第二個示例開始顯示
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
的任意整數值,但是對於我們簡單的程式,實際上只有兩個值有用,True
或 False
。讓我們相應地修改程式碼
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
我們帶回了一個位置引數,因此出現了抱怨。
請注意,順序無關緊要。
我們如何讓我們的程式恢復具有多個詳細程度值的能力,並實際使用它們
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=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", action="count",
help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
# bugfix: replace == with >=
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 -vvv
the square of 4 equals 16
$ python prog.py 4 -vvvv
the square of 4 equals 16
$ python prog.py 4
Traceback (most recent call last):
File "prog.py", line 11, in <module>
if args.verbosity >= 2:
TypeError: '>=' not supported between instances of 'NoneType' and 'int'
第一次輸出正常,並修復了我們之前的錯誤。也就是說,我們希望任何值 >= 2 時都儘可能詳細。
第三次輸出不太好。
讓我們修復那個錯誤
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
help="display a square of a given number")
parser.add_argument("-v", "--verbosity", action="count", default=0,
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)
我們剛剛引入了另一個關鍵字,default
。我們將其設定為 0
,以便使其與其他 int 值可比較。請記住,預設情況下,如果未指定可選引數,則它將獲得 None
值,並且該值無法與 int 值進行比較(因此會引發 TypeError
異常)。
並且
$ python prog.py 4
16
僅憑我們目前學到的知識,你就可以走得很遠,而我們只是觸及了皮毛。argparse
模組非常強大,在結束本教程之前,我們將對其進行更多探索。
更進一步¶
如果我們想擴充套件我們的小程式來執行其他冪運算,而不僅僅是平方,該怎麼辦
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
parser.add_argument("-v", "--verbosity", action="count", default=0)
args = parser.parse_args()
answer = args.x**args.y
if args.verbosity >= 2:
print(f"{args.x} to the power {args.y} equals {answer}")
elif args.verbosity >= 1:
print(f"{args.x}^{args.y} == {answer}")
else:
print(answer)
輸出
$ python prog.py
usage: prog.py [-h] [-v] x y
prog.py: error: the following arguments are required: x, y
$ python prog.py -h
usage: prog.py [-h] [-v] x y
positional arguments:
x the base
y the exponent
options:
-h, --help show this help message and exit
-v, --verbosity
$ python prog.py 4 2 -v
4^2 == 16
請注意,到目前為止,我們一直在使用詳細級別來更改顯示的文字。以下示例改為使用詳細級別來顯示更多文字
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
parser.add_argument("-v", "--verbosity", action="count", default=0)
args = parser.parse_args()
answer = args.x**args.y
if args.verbosity >= 2:
print(f"Running '{__file__}'")
if args.verbosity >= 1:
print(f"{args.x}^{args.y} == ", end="")
print(answer)
輸出
$ python prog.py 4 2
16
$ python prog.py 4 2 -v
4^2 == 16
$ python prog.py 4 2 -vv
Running 'prog.py'
4^2 == 16
指定模糊引數¶
當在決定引數是位置引數還是用於某個引數時存在歧義時,可以使用 --
來告訴 parse_args()
之後的所有內容都是位置引數
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('-n', nargs='+')
>>> parser.add_argument('args', nargs='*')
>>> # ambiguous, so parse_args assumes it's an option
>>> parser.parse_args(['-f'])
usage: PROG [-h] [-n N [N ...]] [args ...]
PROG: error: unrecognized arguments: -f
>>> parser.parse_args(['--', '-f'])
Namespace(args=['-f'], n=None)
>>> # ambiguous, so the -n option greedily accepts arguments
>>> parser.parse_args(['-n', '1', '2', '3'])
Namespace(args=[], n=['1', '2', '3'])
>>> parser.parse_args(['-n', '1', '--', '2', '3'])
Namespace(args=['2', '3'], n=['1'])
衝突選項¶
到目前為止,我們一直在使用 argparse.ArgumentParser
例項的兩種方法。讓我們介紹第三種方法,add_mutually_exclusive_group()
。它允許我們指定彼此衝突的選項。我們還將更改程式的其餘部分,以便新功能更有意義:我們將引入 --quiet
選項,它將與 --verbose
選項相反
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
args = parser.parse_args()
answer = args.x**args.y
if args.quiet:
print(answer)
elif args.verbose:
print(f"{args.x} to the power {args.y} equals {answer}")
else:
print(f"{args.x}^{args.y} == {answer}")
我們的程式現在更簡單了,並且為了演示的目的,我們失去了一些功能。無論如何,這是輸出
$ python prog.py 4 2
4^2 == 16
$ python prog.py 4 2 -q
16
$ python prog.py 4 2 -v
4 to the power 2 equals 16
$ python prog.py 4 2 -vq
usage: prog.py [-h] [-v | -q] x y
prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
$ python prog.py 4 2 -v --quiet
usage: prog.py [-h] [-v | -q] x y
prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
這應該很容易理解。我添加了最後一個輸出,以便你可以看到你獲得的靈活性,即混合長格式選項和短格式選項。
在結束之前,你可能想告訴你的使用者你的程式的主要目的,以防他們不知道
import argparse
parser = argparse.ArgumentParser(description="calculate X to the power of Y")
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
args = parser.parse_args()
answer = args.x**args.y
if args.quiet:
print(answer)
elif args.verbose:
print(f"{args.x} to the power {args.y} equals {answer}")
else:
print(f"{args.x}^{args.y} == {answer}")
請注意用法文字中的細微差別。請注意 [-v | -q]
,它告訴我們可以使用 -v
或 -q
,但不能同時使用
$ python prog.py --help
usage: prog.py [-h] [-v | -q] x y
calculate X to the power of Y
positional arguments:
x the base
y the exponent
options:
-h, --help show this help message and exit
-v, --verbose
-q, --quiet
如何翻譯 argparse 輸出¶
argparse
模組的輸出,例如其幫助文字和錯誤訊息,都使用 gettext
模組進行翻譯。這使應用程式可以輕鬆地本地化 argparse
生成的訊息。另請參閱 國際化你的程式和模組。
例如,在這個 argparse
輸出中
$ python prog.py --help
usage: prog.py [-h] [-v | -q] x y
calculate X to the power of Y
positional arguments:
x the base
y the exponent
options:
-h, --help show this help message and exit
-v, --verbose
-q, --quiet
字串 usage:
、positional arguments:
、options:
和 show this help message and exit
都是可翻譯的。
為了翻譯這些字串,必須首先將它們提取到 .po
檔案中。例如,使用 Babel,執行此命令
$ pybabel extract -o messages.po /usr/lib/python3.12/argparse.py
此命令將從 argparse
模組中提取所有可翻譯的字串,並將它們輸出到名為 messages.po
的檔案中。此命令假設你的 Python 安裝在 /usr/lib
中。
你可以使用此指令碼找出系統上 argparse
模組的位置
import argparse
print(argparse.__file__)
自定義型別轉換器¶
argparse
模組允許你為命令列引數指定自定義型別轉換器。這允許你在使用者輸入儲存到 argparse.Namespace
中之前修改使用者輸入。當你需要在程式中使用輸入之前對其進行預處理時,這很有用。
使用自定義型別轉換器時,你可以使用任何接受單個字串引數(引數值)並返回轉換值的可呼叫物件。但是,如果你需要處理更復雜的場景,你可以使用帶有 action 引數的自定義操作類。
例如,假設你想處理帶有不同字首的引數並相應地處理它們
import argparse
parser = argparse.ArgumentParser(prefix_chars='-+')
parser.add_argument('-a', metavar='<value>', action='append',
type=lambda x: ('-', x))
parser.add_argument('+a', metavar='<value>', action='append',
type=lambda x: ('+', x))
args = parser.parse_args()
print(args)
輸出
$ python prog.py -a value1 +a value2
Namespace(a=[('-', 'value1'), ('+', 'value2')])
在這個例子中,我們
使用
prefix_chars
引數建立了一個具有自定義字首字元的解析器。定義了兩個引數,
-a
和+a
,它們使用type
引數來建立自定義型別轉換器,以將值儲存在帶有字首的元組中。
如果沒有自定義型別轉換器,引數會將 -a
和 +a
視為同一引數,這是不希望的。透過使用自定義型別轉換器,我們能夠區分這兩個引數。
結論¶
argparse
模組提供的功能遠不止此處所示。它的文件非常詳細和全面,並且充滿了示例。在學習完本教程後,你應該能夠輕鬆地理解它們,而不會感到不知所措。