1. 程式人生 > 實用技巧 >argparse模組簡明教程

argparse模組簡明教程

前言:閱讀原始碼時遇到argparse庫,我為了理解用法找了不少部落格,要麼晦澀難懂,要麼講的太淺。最後還是看了官方英文教程,講得非常清楚。本文大部分內容都來源於官方教程,我做了小小的翻譯和整理工作,為英文不太好的同學節省一下時間。這篇文章略長,但我相信只要你從頭讀到尾,一定可以很好地理解argparse的用法。
轉載請註明出處哦

1 argparse是幹什麼的

我們在寫python程式的時候,有時候希望在執行的時候附加一些引數。為了在程式中使用這些附加引數,我們需要正確地解析它們,這個時候argparse就派上用場了,argparse是一款簡單而又強大的命令列解析工具。

2 命令列引數的概念

首先,讓我們看看Linux中常用的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資料夾裡的內容,該怎麼做呢?實際上我們指定了一個位置引數 pypy,之所以叫位置引數,是因為僅僅憑藉它的位置資訊,解析器就知道該怎麼做。就比如cp命令,基礎的使用方法是cp SRC DEST,第一個位置是想要複製的檔案,第二個位置是想要複製到的地方。
  • 現在,我們想要改變程式的行為。在上面的例子中,我們想展示每個檔案的更多資訊,而不僅僅是檔名。-l做了這件事,它被稱為可選引數
  • 指定--help會展示幫助文件,當遇到一個從來沒用過的程式時,幫助文件能夠讓你明白程式如何運作。

3 從一個簡單的例子開始

讓我們從一個簡單到幾乎什麼也不做的例子,來開始argparse的學習:

# prog.py
import argparse
parser = argparse.ArgumentParser()
parser.parse_args()

下面是我們從命令列執行程式碼的結果:

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

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

觀察上面的程式碼和執行過程,你能發現什麼呢?

  • 當不加引數來執行指令碼時,什麼都不會列印到標準輸出上,程式看起來沒什麼用
  • 第二個命令展現了argparse的用處,我們在prog.py腳本里幾乎什麼也沒做,但是我們已經擁有了一個幫助文件,只要指定--help就可以展示它
  • --help引數也可以用更簡短的-h來替代,它們是argparse唯一的免費引數,哈哈,之所以說是免費,是因為我們無需在程式中定義它們的行為就擁有了它們,觀察prog.py,並沒有任何和--help相關的程式碼。但是,除了--help-h之外,指定任何其他的附加引數都會報錯,因為我們沒有在程式中定義它們。接下來我會介紹如何定義自己的引數。

4 介紹位置引數

看這個例子:

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

命令列執行程式碼:

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

positional arguments:
  echo

optional arguments:
  -h, --help  show this help message and exit
$ python3 prog.py foo
foo

觀察上面的程式碼和執行過程,你發現了什麼呢?

  • 我們加了一個add_argument函式,這個函式允許我們指定自己的命令列引數。在這個例子中,我指定了一個名叫echo的引數
  • 現在我們執行程式會要求我們給出引數echo的值,不然就像第一行一樣報錯:the following arguments are required: echo
  • parse_args()會返回使用者給出的引數值,在這個例子中,是echo引數的值。
  • parse_args()返回的引數的值可以通過 .引數名 獲得,這個例子裡,我們通過args.echo就可以得到echo的引數值,是我們傳的字串'foo'。注意,這個引數名就是我們在add_argument函式裡給出的字串'echo'。
    看看上面--help輸出的幫助文件,雖然看起來已經很不錯了,但是並不是很有幫助。比如,通過幫助文件,我們知道echo是一個位置引數,但是並不知道它是什麼,除了看prog.py的原始碼我們根本不知道這個引數的作用是啥。所以,來讓它更有用吧:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo", help="echo the string you use here")
args = parser.parse_args()
print(args.echo)

然後我們就會看到:

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

positional arguments:
  echo        echo the string you use here

optional arguments:
  -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)

上面的程式功能是從命令列讀取一個位置引數square,然後列印它的平方值。
看看執行過程:

$ python3 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應該將輸入視作int型別:

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)

然後執行程式碼:

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

哇,很完美有沒有,程式現在可以很好地運作了。而且你發現沒,當我們輸入four作為引數時,這不是一個數字,如果不使用argparse,我們要到運算平方的時候才報型別錯誤,現在在引數解析的時候程式就會因為型別不匹配提前退出,很棒吧。

5 介紹可選引數

到目前為止我們一直在和位置引數打交道,讓我們看看怎麼新增可選引數吧!

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

執行程式:

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

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

觀察上面的程式碼和執行過程,你發現了什麼呢?

  • 這個程式希望的功能是,當指定--verbosity輸出一些東西,反之什麼也不輸出
  • 定義可選引數的方法是在引數名前面加兩道橫槓--
  • 為了證明可選引數真的是可選的,看上面的執行過程,當我們不指定--verbosity的時候程式沒有報錯,這就說明可選引數真的是可選的嗷。注意!當我們在程式中定義了可選引數,但是在執行的時候沒有用它,比如這個例子中的args.verbosity,那麼它的預設值是None,這也就是在不指定--verbosity時,if語句會False的原因。

上面的例子中,我們需要接受一個任意值給--verbosity。但實際上,我們希望指定--verbosity時,args.verbosity為True,不指定為False就夠了。能不能不指定一個值給--verbosity呢?看看這個例子

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

然後執行:

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

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

觀察上面的程式碼和執行過程,發現了什麼?

  • 現在這個--verbose選項更像是一個flag(標誌),而不是需要指定一個值的引數。我們用了一個新keyword,action,並且給了它一個值"store_true",這意味著,如果執行程式時指定了--verbose標誌,就把True賦給args.verbose,如果沒有指定,就把False賦給它
  • 現在,如果你還要給--verbose指定一個值,它就會報錯!
  • 注意幫助文件的變化

簡短引數(short options)

如果你熟悉命令列的操作,你就會注意到,截至目前,我還沒有討論簡短引數。什麼是簡短引數呢?比如--help,可以用-h代替,一個橫槓開頭的-h就是簡短引數。其實定義簡短引數非常簡單:

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

然後就會這樣:

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

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

注意幫助文件的變化。

6 結合使用位置引數和可選引數

來看一個更復雜的例子:

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("the square of {} equals {}".format(args.square, answer))
else:
    print(answer)

執行程式碼:

$ python3 prog.py
usage: prog.py [-h] [-v] square
prog.py: error: the following arguments are required: square
$ python3 prog.py 4
16
$ python3 prog.py 4 --verbose
the square of 4 equals 16
$ python3 prog.py --verbose 4
the square of 4 equals 16
  • 現在,我們定義了位置引數square,所以你不給它指定一個值程式就會報錯
  • 注意,可選引數並不影響位置引數的位置,所以它們的順序無所謂。
    好了,我們來試試讓--verbosity不再是flag,而是給它指定一個數字,如果數字越大,程式輸出的屁話(誤)越多。修改程式如下:
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("the square of {} equals {}".format(args.square, answer))
elif args.verbosity == 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

看出區別了嗎,我們把--verbosity的type設成了int,並且把action='store_true'去掉了。這樣就要指定一個數字給它。
執行程式碼:

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

看起來很不錯!除了最後一個,我們不希望賦3給verbosity時它反而輸出一個更短的句子,這是我們程式中的一個bug。讓我們來修復這個bug,將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("the square of {} equals {}".format(args.square, answer))
elif args.verbosity == 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

執行一下程式碼:

$ python3 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)
$ python3 prog.py 4 -h
usage: prog.py [-h] [-v {0,1,2}] square

positional arguments:
  square                display a square of a given number

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

注意報錯資訊和幫助文件的變化。

好,接下來,讓我們來看看還能把這個verbosity玩出什麼花活,其實也是很常用的玩法,CPython直譯器的verbosity引數就是這種玩法,你可以執行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("the square of {} equals {}".format(args.square, answer))
elif args.verbosity == 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

這裡引入了另一個新的action,“count”,去計算選項被指定的次數。看下面的執行過程你就懂了:

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

positional arguments:
  square           display a square of a given number

optional arguments:
  -h, --help       show this help message and exit
  -v, --verbosity  increase output verbosity
$ python3 prog.py 4 -vvv
16
  • 現在-v/--verbosity看起來更像是一個flag(和action='store_true'有點類似),當你給--verbosity指定一個值就會報錯。
  • 注意,如果你不指定-v或者--verbosity,args.verbosity的值不是0,而是None!這會導致Bug,待會說。
  • 根據我們程式的需要,當指定三個以上的v,也應該和指定兩個v的輸出相同(兩個v就輸出了很多屁話了,說不出更多的屁話了)
  • 有點不幸的是,幫助文件這次沒有表現出有用的資訊,它沒告訴我們可以多次指定v來獲得更詳盡的輸出。
  • 最後一個執行結果也是我們程式的一個bug,指定了三個v卻得到很短的輸出。
    讓我們來修復一下bug:
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("the square of {} equals {}".format(args.square, answer))
elif args.verbosity >= 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

然後執行:

$ python3 prog.py 4 -vvv
the square of 4 equals 16
$ python3 prog.py 4 -vvvv
the square of 4 equals 16
$ python3 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'
  • 前兩個輸出很不錯,修復了我們之前的bug
  • 第三個輸出存在問題。這就是我上面提到的,如果不指定-v,args.verbosity的值是None,而不是0!
    讓我們修復第三個輸出的問題:
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("the square of {} equals {}".format(args.square, answer))
elif args.verbosity >= 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

我們引入了一個新的keyword,default,我們將它設為0,這樣在不指定-v時,args.verbosity被設為0,就可以和其他數字比較了!記住,如果某個可選引數在執行時沒有被指定,就會賦None值給它,而None是無法和int值做比較的!解決辦法就是給它一個defualt值。
現在:

$ python3 prog.py 4
16

總結

根據上面所學的知識,你已經可以做非常非常多的事了,但實際上我們也只涉及到argparse強大功能的表面而已。argparse非常強大,如果有需要,大夥可以查閱argparse的官方api文件
碼字不易,如果覺得文章不錯的話,點個贊再走吧,秋梨膏!