Google 開源的 Python 命令列庫:深入 fire(二)
作者:HelloGitHub-Prodesire
HelloGitHub 的《講解開源專案》系列,專案地址:https://github.com/HelloGitHub-Team/Article
一、前言
在上一篇文章中我們介紹了 fire
的子命令、巢狀命令和屬性訪問等內容,今天我們將繼續深入瞭解 fire
的其他功能。
本系列文章預設使用 Python 3 作為直譯器進行講解。
若你仍在使用 Python 2,請注意兩者之間語法和庫的使用差異哦~
二、功能
2.1 最簡命令實現
在上一節中,我們介紹了只要定義一個函式就可以實現命令列程式。比如:
import fire def english(): return 'Hello, fire!' def chinese(): return '你好,fire!' if __name__ == '__main__': fire.Fire()
但這還不是最簡單的實現方式,fire
甚至允許你通過定義變數的方式來實現命令列!
上面的例子可以寫成下面這種形式:
import fire
english = 'Hello, fire!'
chinese = '你好,fire!'
if __name__ == '__main__':
fire.Fire()
2.2 鏈式呼叫
在 Fire CLI
中,你可以通過鏈式呼叫不斷地對上一個結果進行處理。
想做到這一點也很簡單,就是在例項方法中返回 self
即可。
在下面的示例中,我們實現了一個簡單的四則運算命令,可鏈式呼叫 add
、sub
、mul
和 div
。
import fire class Calculator: def __init__(self): self.result = 0 self.express = '0' def __str__(self): return f'{self.express} = {self.result}' def add(self, x): self.result += x self.express = f'{self.express}+{x}' return self def sub(self, x): self.result -= x self.express = f'{self.express}-{x}' return self def mul(self, x): self.result *= x self.express = f'({self.express})*{x}' return self def div(self, x): self.result /= x self.express = f'({self.express})/{x}' return self if __name__ == '__main__': fire.Fire(Calculator)
上述程式碼中的 add
、sub
、mul
、div
分別對應加、減、乘、除的邏輯,每個方法都接受 x
引數作為參與運算的數字,返回值均為 self
,這樣就可以無限次地鏈式呼叫。在命令列中鏈式呼叫結束後,會最終呼叫到 __str__
方法將結果打印出來。
其中,__str__
在 fire
中用來完成自定義序列化。如果不提供這個方法,在鏈式呼叫完成後將會列印幫助內容。
比如,我們可以這麼呼叫:
$ python calculator.py add 1 sub 2 mul 3 div 4 ((+1-2)*3)/4 = -0.75 $ python calculator.py add 1 sub 2 mul 3 div 4 add 4 sub 3 mul 2 div 1 ((((0+1-2)*3)/4+4-3)*2)/1 = 0.5
2.3 位置引數和選項引數
通過前面的介紹我們也都清楚了在 fire
中不必顯式的定義位置引數或選項引數。
通過下面的例子,我們將細化兩類引數的使用:
import fire
class Building(object):
def __init__(self, name, stories=1):
self.name = name
self.stories = stories
def __str__(self):
return f'name: {self.name}, stories: {self.stories}'
def climb_stairs(self, stairs_per_story=10):
yield self.name
for story in range(self.stories):
for stair in range(1, stairs_per_story):
yield stair
yield 'Phew!'
yield 'Done!'
if __name__ == '__main__':
fire.Fire(Building)
- 建構函式中定義的引數(如
name
和stories
)在命令列中僅為選項引數(如--name
和--stories
)。我們可以這麼呼叫:
$ python example.py --name="Sherrerd Hall" --stories=3
- 建構函式中定義的引數可在命令中放於任意位置。比如下面兩個呼叫都是可以的:
$ python example.py --name="Sherrerd Hall" climb-stairs --stairs-per-story 10
$ python example.py climb-stairs --stairs-per-story 10 --name="Sherrerd Hall"
- 建構函式和普通方法中定義的預設引數(如
stories
),在命令列中是可選的。我們可以這麼呼叫:
$ python example.py --name="Sherrerd Hall"
- 普通方法中定義的引數(如
stairs_per_story
)在命令列中即可以是位置引數,也可以是選項引數。我們可以這麼呼叫:
# 作為位置引數
$ python example.py --name="Sherrerd Hall" climb_stairs 10
# 作為選項引數
$ python example.py --name="Sherrerd Hall" climb_stairs --stairs_per_story=10
- 選項引數中的橫槓(
-
)和下劃線(_
)是等價的。因此也可以這麼呼叫:
# 作為選項引數
$ python example.py --name="Sherrerd Hall" climb_stairs --stairs-per-story=10
此外,fire
還支援在函式中定義 *args
和 **kwargs
。
import fire
def fargs(*args):
return str(args)
def fkwargs(**kwargs):
return str(kwargs)
if __name__ == '__main__':
fire.Fire()
- 函式中的
*args
在命令列中為位置引數。我們可以這麼呼叫:
$ python example.py fargs a b c
- 函式中的
**kwargs
在命令列中為選項引數。我們可以這麼呼叫:
$ python example.py fargs --a a1 --b b1 --c c1
- 通過分隔符
-
可顯式告知分隔符後的為子命令,而非命令的引數。且看下面的示例:
# 沒有使用分隔符,upper 被作為位置引數
$ python example.py fargs a b c upper
('a', 'b', 'c', 'upper')
# 使用了分隔符,upper 被作為子命令
$ python example.py fargs a b c - upper
('A', 'B', 'C')
- 通過
fire
內建的--separator
可以自定義分隔符,此選項引數需要跟在單獨的--
後面:
$ python example.py a b c X upper -- --separator=X
('A', 'B', 'C')
2.4 引數型別
在 fire
中,引數的型別由其值決定,通過下面的簡單程式碼,我們可以看到給不同的值時,fire
會解析為什麼型別:
import fire
fire.Fire(lambda obj: type(obj).__name__)
$ python example.py 10
int
$ python example.py 10.0
float
$ python example.py hello
str
$ python example.py '(1,2)'
tuple
$ python example.py [1,2]
list
$ python example.py True
bool
$ python example.py {name: David}
dict
如果想傳遞字串形式的數字,那就需要小心引號了,要麼把引號引起來,要麼轉義引號:
# 數字 10
$ python example.py 10
int
# 沒有對引號處理,仍然是數字10
$ python example.py "10"
int
# 把引號引起來,所以是字串“10”
$ python example.py '"10"'
str
# 另一種把引號引起來的形式
$ python example.py "'10'"
str
# 轉義引號
$ python example.py \"10\"
str
考慮下更復雜的場景,如果傳遞的是字典,在字典中有字串,那麼也是要小心引號的:
# 推薦做法
$ python example.py '{"name": "David Bieber"}'
dict
# 也是可以的
$ python example.py {"name":'"David Bieber"'}
dict
# 錯誤,會被解析為字串
$ python example.py {"name":"David Bieber"}
str
# 錯誤,不會作為單個引數(因為中間有空格),報錯
$ python example.py {"name": "David Bieber"}
<error>
如果值為 True
或 False
將為視為布林值,fire
還支援通過 --name
將 name
設為 True
,或通過 --noname
將 name
設為 False
:
$ python example.py --obj=True
bool
$ python example.py --obj=False
bool
$ python example.py --obj
bool
$ python example.py --noobj
bool
2.5 Fire 內建選項引數
Fire 內建了一些選項引數,以幫助我們更容易地使用命令列程式。若想使用內建的選項功能,需要將選項引數跟在 --
後,在上文中,我們介紹了 --separator
引數,除了它,fire
還支援以下選項引數:
command -- --help
列出詳細的幫助資訊command -- --interactive
進入互動式模式command -- --completion [shell]
生成 CLI 程式的自動補全指令碼,以支援自動補全command -- --trace
獲取命令的 Fire 追蹤以瞭解呼叫 Fire 後究竟發生了什麼command -- --verbose
獲取包含私有成員在內的詳情
三、小結
fire
讓命令列程式的實現變得特別簡單,本文著重介紹了它的鏈式呼叫、選項引數、位置引數、引數型別以及內建選項引數。fire
的概念並不多,真正踐行了“把簡單留給他人,把複雜留給自己”的理念。
fire
的介紹就告一段落,它絕對會是你編寫命令列程式的一大利器。在下一篇文章中,我們依然會通過實現一個簡單的 git
程式來進行 fire
的實戰。
『講解開源專案系列』——讓對開源專案感興趣的人不再畏懼、讓開源專案的發起者不再孤單。跟著我們的文章,你會發現程式設計的樂趣、使用和發現參與開源專案如此簡單。歡迎留言聯絡我們、加入我們,讓更多人愛上開源、貢獻開源