Google 開源的 Python 命令行庫:深入 fire(二)

Google 開源的 Python 命令行庫:深入 fire(二)

作者:HelloGitHub-Prodesire

一、前言

在上一篇文章中我們介紹了 fire 的子命令、嵌套命令和屬性訪問等內容,今天我們將繼續深入瞭解 fire 的其他功能。

<code>本系列文章默認使用 Python 3 作為解釋器進行講解。若你仍在使用 Python 2,請注意兩者之間語法和庫的使用差異哦~/<code>

二、功能

2.1 最簡命令實現

在上一節中,我們介紹了只要定義一個函數就可以實現命令行程序。比如:

<code>import firedef english():  return 'Hello, fire!'def chinese():  return '你好,fire!'if __name__ == '__main__':  fire.Fire()/<code>

但這還不是最簡單的實現方式,fire 甚至允許你通過定義變量的方式來實現命令行!上面的例子可以寫成下面這種形式:

<code>import fireenglish = 'Hello, fire!'chinese = '你好,fire!'if __name__ == '__main__':  fire.Fire()/<code>

2.2 鏈式調用

在 Fire CLI 中,你可以通過鏈式調用不斷地對上一個結果進行處理。

想做到這一點也很簡單,就是在實例方法中返回 self 即可。

在下面的示例中,我們實現了一個簡單的四則運算命令,可鏈式調用 add、sub、mul 和 div。

<code>import fireclass 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 selfif __name__ == '__main__':  fire.Fire(Calculator)/<code>

上述代碼中的 add、sub、mul、div 分別對應加、減、乘、除的邏輯,每個方法都接受 x 參數作為參與運算的數字,返回值均為 self,這樣就可以無限次地鏈式調用。在命令行中鏈式調用結束後,會最終調用到 __str__ 方法將結果打印出來。

其中,__str__ 在 fire 中用來完成自定義序列化。如果不提供這個方法,在鏈式調用完成後將會打印幫助內容。

比如,我們可以這麼調用:

<code>$ 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/<code>

2.3 位置參數和選項參數

通過前面的介紹我們也都清楚了在 fire 中不必顯式的定義位置參數或選項參數。

通過下面的例子,我們將細化兩類參數的使用:

<code>import fireclass 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)/<code>
  • 構造函數中定義的參數(如 name 和 stories)在命令行中僅為選項參數(如 --name 和 --stories)。我們可以這麼調用:
<code>$ python example.py --name="Sherrerd Hall" --stories=3/<code>
  • 構造函數中定義的參數可在命令中放於任意位置。比如下面兩個調用都是可以的:
<code>$ python example.py --name="Sherrerd Hall" climb-stairs --stairs-per-story 10$ python example.py climb-stairs --stairs-per-story 10 --name="Sherrerd Hall"/<code>
  • 構造函數和普通方法中定義的默認參數(如 stories),在命令行中是可選的。我們可以這麼調用:
<code>$ python example.py --name="Sherrerd Hall"/<code>
  • 普通方法中定義的參數(如 stairs_per_story)在命令行中即可以是位置參數,也可以是選項參數。我們可以這麼調用:
<code># 作為位置參數$ python example.py --name="Sherrerd Hall" climb_stairs 10# 作為選項參數$ python example.py --name="Sherrerd Hall" climb_stairs --stairs_per_story=10/<code>
  • 選項參數中的橫槓(-)和下劃線(_)是等價的。因此也可以這麼調用:
<code># 作為選項參數$ python example.py --name="Sherrerd Hall" climb_stairs --stairs-per-story=10/<code>

此外,fire 還支持在函數中定義 *args 和 **kwargs。

<code>import firedef fargs(*args):  return str(args)def fkwargs(**kwargs):  return str(kwargs)if __name__ == '__main__':  fire.Fire()/<code>
  • 函數中的 *args 在命令行中為位置參數。我們可以這麼調用:
<code>$ python example.py fargs a b c/<code>
  • 函數中的 **kwargs 在命令行中為選項參數。我們可以這麼調用:
<code>$ python example.py fargs --a a1 --b b1 --c c1/<code>
  • 通過分隔符 - 可顯式告知分隔符後的為子命令,而非命令的參數。且看下面的示例:
<code># 沒有使用分隔符,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')/<code>
  • 通過 fire 內置的 --separator 可以自定義分隔符,此選項參數需要跟在單獨的 -- 後面:
<code>$ python example.py a b c X upper -- --separator=X('A', 'B', 'C')/<code>

2.4 參數類型

在 fire 中,參數的類型由其值決定,通過下面的簡單代碼,我們可以看到給不同的值時,fire會解析為什麼類型:

<code>import firefire.Fire(lambda obj: type(obj).__name__)/<code>
<code>$ python example.py 10int$ python example.py 10.0float$ python example.py hellostr$ python example.py '(1,2)'tuple$ python example.py [1,2]list$ python example.py Truebool$ python example.py {name: David}dict/<code> 

如果想傳遞字符串形式的數字,那就需要小心引號了,要麼把引號引起來,要麼轉義引號:

<code># 數字 10$ python example.py 10int# 沒有對引號處理,仍然是數字10$ python example.py "10"int# 把引號引起來,所以是字符串“10”$ python example.py '"10"'str# 另一種把引號引起來的形式$ python example.py "'10'"str# 轉義引號$ python example.py \"10\"str/<code>

考慮下更復雜的場景,如果傳遞的是字典,在字典中有字符串,那麼也是要小心引號的:

<code># 推薦做法$ 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>/<code>

如果值為 True 或 False 將為視為布爾值,fire 還支持通過 --name 將 name 設為 True,或通過 --noname 將 name 設為 False:

<code>$ python example.py --obj=Truebool$ python example.py --obj=Falsebool$ python example.py --objbool$ python example.py --noobjbool/<code>

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 的實戰。


『講解開源項目系列』——讓對開源項目感興趣的人不再畏懼、讓開源項目的發起者不再孤單。跟著我們的文章,你會發現編程的樂趣、使用和發現參與開源項目如此簡單。歡迎留言聯繫我們、加入我們,讓更多人愛上開源、貢獻開源~


分享到:


相關文章: