天天看點

Python 指令行之旅:深入 docoptPython 指令行之旅:深入 docopt

Python 指令行之旅:深入 docopt

原文發表于

Prodesire 部落格

一、前言

在第一篇“初探 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

指令。