讓你如「老」紳士般編寫 Python 命令行工具的開源項目:docopt
- 2019 年 10 月 22 日
- 筆記
作者:HelloGitHub-Prodesire
HelloGitHub 的《講解開源項目》系列,項目地址:https://github.com/HelloGitHub-Team/Article
一、前言
在第一篇「初探 docopt」的文章中,我們初步掌握了使用 docopt
的三個步驟,了解了它不同於 argparse
的設計思路。
那麼 docopt
的使用模式都有哪些呢?其介面描述中都支援哪些語法規則呢?本文將帶你深入了解 docopt
。
本系列文章默認使用 Python 3 作為解釋器進行講解。 若你仍在使用 Python 2,請注意兩者之間語法和庫的使用差異哦~
二、使用模式
在上一篇文章中我們提到 docopt
是通過定義一個包含特定內容的字元串,也就是介面描述,來達到描述命令行功能的目的。
那麼介面描述的總體規則是這樣的:
- 位於關鍵字
usage:
(大小寫不敏感)和一個可見的空行之間的文本內容會被解釋為一個個使用模式。 useage:
後的第一個詞會被解釋為程式的名稱,比如下面就是一個沒有命令行參數的示常式序:
Usage: cli
- 介面描述中可以包含很多有各種元素的模式,以描述命令行用法,比如:
Usage: cli command --option <argument> cli [<optional-argument>] cli --another-option=<with-argument> cli (--either-that-option | <or-this-argument>) cli <repeating-argument> <repeating-argument>...
2.1 位置參數:
使用 <
和 >
包裹的參數會被解釋為位置參數。
比如,我們可以指定兩個位置參數 x
和 y
,先添加的 x
位於第一個位置,後加入的 y
位於第二個位置。那麼在命令行中輸入 1 2
的時候,分別對應到的就是 x
和 y
:
""" Usage: cli <x> <y> """ from docopt import docopt arguments = docopt(__doc__, argv=['1', '2']) print(arguments)
其輸出為:
{'<x>': '1', '<y>': '2'}
2.2 選項參數:-o –option
以單個破折號(-
)開頭的的參數為短選項,以雙破折號(--
)開頭的參數為長選項。
- 短選項支援集中表達多個短選項,比如
-abc
等價於-a
、-b
和-c
- 長選項後可跟參數,通過
空格
或=
指定,比如--input ARG
等價於--input=ARG
- 短選項後可跟參數,通可選的
空格
指定,比如-f FILE
等價於-fFILE
在下面這個例子中,我們希望通過 -n
h 或 --name
來指定名字:
""" Usage: cli [options] Options: -n, --name NAME Set name. """ from docopt import docopt arguments = docopt(__doc__, argv=['-n', 'Eric']) print(arguments) arguments = docopt(__doc__, argv=['-nEric']) print(arguments) arguments = docopt(__doc__, argv=['--name', 'Eric']) print(arguments) arguments = docopt(__doc__, argv=['--name=Eric']) print(arguments)
上面的示例中,我們通過 4 種方式(2 個短選項參數方式和 2 個長選項參數方式)來指定命令行輸入,其輸出均為:
{'--name': 'Eric'}
需要注意的是:
--input ARG
(而不是 --input=ARG
)的含義是模糊不清的,因為這不能看出 ARG
究竟是選項參數,
還是位置參數。在 docopt
的使用模式中,只有在介面描述中定義了對應選項才會被解釋為一個帶參數的選項,
否則就會被解釋為一個選項和一個獨立的位置參數。
-f FILE
和 -fFILE
這種寫法也有同樣的模糊點。後者無法說明這究竟是一系列短選項的集合,
還是一個帶參數的選項。只有在介面描述中定義了對應選項才會被解釋為一個帶參數的選項。
2.3 命令
這裡的命令也就是 argparse
中嵌套解析器所要完成的事情,準確的說,對整個命令行程式來說,實現的是子命令。
在 docopt
中,凡是不符合 --options
或 <arguments>
約定的詞,均會被解釋為子命令。
在下面這個例子中,我們支援 create
和 delete
兩個子命令,用來創建或刪除指定路徑。而 delete
命令支援 --recursive
參數來表明是否遞歸刪除指定路徑:
""" Usage: cli create cli delete [--recursive] Options: -r, --recursive Recursively remove the directory. """ from docopt import docopt arguments = docopt(__doc__) print(arguments)
直接指定 delete -r
,輸出如下:
$ python3 cli.py delete -r {'--recursive': True, 'create': False, 'delete': True}
2.4 可選元素:[optional elements]
以中括弧「[]」包裹的元素(選項、參數和命令)均會被標記為可選。多個元素放在一對中括弧中或各自放在中括弧中是等價的。比如:
Usage: cli [command --option <argument>]
等價於:
Usage: cli [command] [--option] [<argument>]
2.5 必填元素:(required elements)
沒被中括弧「[]」包裹的所有元素默認都是必填的。但有時候使用小括弧「()」將元素包裹住,用以標記必填是有必要的。
比如,要將多個互斥元素進行分組:
Usage: my_program (--either-this <and-that> | <or-this>)
另一個例子是,當出現一個參數時,也要求提供另一個參數,那麼就可以這麼寫:
Usage: my_program [(<one-argument> <another-argument>)]
這個例子中 <one-argument>
和 <another-argument>
要麼都出現,要麼都不出現。
2.6 互斥參數:element|another
在 argparse
中要想實現互斥參數,還需要先調用 parser.add_mutually_exclusive_group()
添加互斥組,
再在組裡添加參數。而在 docopt
中就特別簡單,直接使用 |
進行分隔:
Usage: my_program go (--up | --down | --left | --right)
在上面的示例中,使用小括弧「()」來對四個互斥選項分組,要求必填其中一個選項。
在下面的示例中,使用中括弧「()」來對四個互斥選項分組,可以不填,或填其中一個選項:
Usage: my_program go [--up | --down | --left | --right]
我們還可以發散一下思路,子命令天然需要互斥,那麼除了這種寫法:
Usage: my_program run [--fast] my_program jump [--high]
使用如下 |
的寫法,也是等價的:
Usage: my_program (run [--fast] | jump [--high])
2.7 可變參數列表:element…
可變參數列表也就是定義參數可以有多個值。在 argparse
中,我們通過 parser.add_argument('--foo', nargs='?')
來指定,其中 nargs
可以是數字、?
、+
、*
來表示參數個數。
在 docopt
中,自然也有相同的能力,使用省略號 ...
來實現:
Usage: my_program open <file>... my_program move (<from> <to>)...
若要參數提供 N 個,則寫 N 個參數即可,比如下面的示例中要求提供 2 個:
Usage: my_program <file> <file>
若要參數提供 0 個或多個,則配合中括弧「[]」進行定義,如下 3 中定義方式等價:
Usage: my_program [<file>...] my_program [<file>]... my_program [<file> [<file> ...]]
若要參數提供 1 個或多個,則可以這麼寫:
Usage: my_program <file>...
在下面完整示例中,所獲得的 arguments
是 {'<file>': ['f1', 'f2']}
:
""" Usage: cli <file>... """ from docopt import docopt arguments = docopt(__doc__, argv=['f1', 'f2']) print(arguments)
2.8 選項簡寫: [options]
「[options]」用於簡寫選項,比如下面的示例中定義了 3 個選項:
Usage: my_program [--all --long --human-readable] <path> --all List everything. --long Long output. --human-readable Display in human-readable format.
可以簡寫為:
Usage: my_program [options] <path> --all List everything. --long Long output. --human-readable Display in human-readable format.
如果一個模式中有多個選項,那麼這會很有用。
另外,如果選項包含長短選項,那麼也可以用它們中的任意一個寫在模式中,比如下面的示例的模式中均使用短選項:
Usage: my_program [-alh] <path> -a, --all List everything. -l, --long Long output. -h, --human-readable Display in human-readable format.
2.9 [–]
當雙破折號「–」不是選項時,通常用於分隔選項和位置參數,以便處理例如將文件名誤認為選項的情況。
為了支援此約定,需要在位置參數前添加 [--]
。
Usage: my_program [options] [--] <file>...
2.10 [-]
當單破折號「-」不是選項時,通常用於表示程式應處理 stdin
,而非文件。為了支援此約定,需要在使用模式中加入 [-]
。
2.11 選項描述
選項描述就是描述一系列選項參數的模式。如果使用模式中的選項定義是清晰的,那麼選項描述就是可選的。
選項描述可以定義如下內容:
- 短選項和長選項代表相同含義
- 帶參數的選項
- 有默認值的選項參數
選項描述的每一行需要以 -
或 --
開頭(不算空格),比如:
Options: --verbose # 好 -o FILE # 好 Other: --bad # 壞, 沒有以 "-" 開頭
選項描述中,使用空格或「=」來連接選項和參數,以定義帶選項的參數。參數可以使用 <Arg>
的形式,
或是使用 ARG
大寫字母的形式。可用逗號「,」來分隔長短選項。比如:
-o FILE --output=FILE # 沒有逗號 長選項使用 "=" 分隔 -i <file>, --input <file> # 有逗號, 長選項使用空格分隔
選項描述中每個選項定義和說明之間要有兩個空格,比如:
--verbose MORE text. # 壞, 會被認為是帶參數 MORE 的選項 # --version 和 MORE text. 之間應該有2個空格 -q Quit. # 好 -o FILE Output file. # 好 --stdout Use stdout. # 好,2個空格
選項描述中在說明中使用 [default: <default-value>]
來給帶參數的選項賦以默認值,比如:
--coefficient=K The K coefficient [default: 2.95] --output=FILE Output file [default: test.txt] --directory=DIR Some directory [default: ./]
三、小結
關於 docopt
的方方面面我們都了解的差不多了,回過頭來看。對於命令行元資訊的定義,它比 argparse
要來的更加簡潔。
argparse
像是命令式編程,調用一個個的函數逐步將命令行元資訊定義清楚;而 docopt
則像是聲明式編程,通過聲明定義命令行元資訊。
兩者站在的維度不同,編程的套路也不盡相同,甚是有趣。
了解了這麼多,也該練練手了。在下篇文章中,我們仍然會以 git
命令作為實戰項目,看看如何使用 docopt
來實現 git
命令。
『講解開源項目系列』——讓對開源項目感興趣的人不再畏懼、讓開源項目的發起者不再孤單。跟著我們的文章,你會發現編程的樂趣、使用和發現參與開源項目如此簡單。歡迎留言聯繫我們、加入我們,讓更多人愛上開源、貢獻開源~