一、前言
在近半年的 Python 命令列旅程中,我們依次學習了 argparse、docopt、click 和 fire 庫的特點和用法,逐步了解到 Python 命令列庫的設計哲學與演變, 本文作為本次旅程的終點,希望從一個更高的視角對這些庫進行橫向對比,總結它們的異同點和使用場景,以期在應對不同場景時能夠分析利弊,選擇合適的庫為己所用,
本系列文章默認使用 Python 3 作為解釋器進行講解, 若你仍在使用 Python 2,請注意兩者之間語法和庫的使用差異哦~
二、設計理念
在討論各個庫的設計理念之前,我們先設計一個計算器程式,其實這個例子在 argparse 庫的第一篇講解中出現過,也就是:
- 命令列程式接受一個位置引數,它能出現多次,且是數字
- 默認情況下,命令列程式會求出給定的一串數字的最大值
- 如果指定了選項引數
--sum,那么就會將求出給定的一串數字的和
希望從各個庫實作該例子的代碼中能進一步體會它們的設計理念,
2.1、argparse
argparse 的設計理念就是提供給你最細粒度的控制,你需要詳細地告訴它引數是選項引數還是位置引數、引數值的型別是什么、該引數的處理動作是怎樣的, 總之,它就像是一個沒有智能分析能力的初代機器人,你需要告訴它明確的資訊,它才會根據給定的資訊去幫助你做事情,
以下示例為 argparse 實作的 計算器程式:
import argparse # 1. 設定決議器 parser = argparse.ArgumentParser(description='Calculator Program.') # 2. 定義引數 # 添加位置引數 nums,在幫助資訊中顯示為 num # 其型別為 int,且支持輸入多個,且至少需要提供一個 parser.add_argument('nums', metavar='num', type=int, nargs='+', help='a num for the accumulator') # 添加選項引數 --sum,該引數被 parser 決議后所對應的屬性名為 accumulate # 若不提供 --sum,默認值為 max 函式,否則為 sum 函式 parser.add_argument('--sum', dest='accumulate', action='store_const', const=sum, default=max, help='sum the nums (default: find the max)') # 3. 決議引數 args = parser.parse_args(['--sum', '1', '2', '3']) print(args) # 結果:Namespace(accumulate=<built-in function sum>, nums=[1, 2, 3]) # 4. 業務邏輯 result = args.accumulate(args.nums) print(result) # 基于上文的 ['--sum', '1', '2', '3'] 引數,accumulate 為 sum 函式,其結果為 6
從上述示例可以看到,我們需要通過 add_argument 很明確地告訴 argparse 引數長什么樣:
- 它是位置引數
nums,還是選項引數--sum - 它的型別是什么,比如
type=int表示型別是 int - 這個引數能重復出現幾次,比如
nargs='+'表示至少提供 1 個 - 引數的是存什么的,比如
action='store_const'表示存常量
然后它才根據給定的這些元資訊來決議命令列引數(也就是示例中的 ['--sum', '1', '2', '3']),
這是很計算機的思維,雖然冗長,但也帶來了靈活性,
2.2、docopt
從 argparse 的理念可以看出,它是命令式的,這時候 docopt 另辟蹊徑,宣告式是不是也可以?一個命令列程式的幫助資訊其實已然包含了這個命令列的完整元資訊,那不就可以通過定義幫助資訊來定義命令列?docopt 就是基于這樣的想法去設計的,
宣告式的好處在于只要你掌握了宣告式的語法,那么定義命令列的元資訊就會很簡單,
以下示例為 docopt 實作的 計算器程式:
# 1. 定義介面描述/幫助資訊 """Calculator Program. Usage: calculator.py [--sum] <num>... calculator.py (-h | --help) Options: -h --help Show help. --sum Sum the nums (default: find the max). """ from docopt import docopt # 2. 決議命令列 arguments = docopt(__doc__, options_first=True, argv=['--sum', '1', '2', '3']) print(arguments) # 結果:{'--help': False, '--sum': True, '<num>': ['1', '2', '3']} # 3. 業務邏輯 nums = (int(num) for num in arguments['<num>']) if arguments['--sum']: result = sum(nums) else: result = max(nums) print(result) # 基于上文的 ['--sum', '1', '2', '3'] 引數,處理函式為 sum 函式,其結果為 6
從上述示例可以看到,我們通過 __doc__ 定義了介面描述,這和 argparse 中 add_argument 是等價的,然后 docopt 便會根據這個元資訊把命令列引數轉換為一個字典,業務邏輯中就需要對這個字典進行處理,
對比與 argparse:
- 對于更為復雜的命令程式,元資訊的定義上
docopt會更加簡單 - 然而在業務邏輯的處理上,由于
argparse在一些簡單引數的處理上會更加便捷(比如示例中的情形),相對來說docopt轉換為字典后就把所有處理交給業務邏輯的方式會更加復雜
2.3、click(免費領取Python自動化學習資料 工具,面試寶典面試技巧,加QQ群,785128166,群內還會大佬技術交流)
命令列程式本質上是定義引數和處理引數,而處理引數的邏輯一定是與所定義的引數有關聯的,那可不可以用函式和裝飾器來實作處理引數邏輯與定義引數的關聯呢?而 click 正好就是以這種使用方式來設計的,
click 使用裝飾器的好處就在于用裝飾器優雅的語法將引數定義和處理邏輯整合在一起,從而暗示了路由關系,相比于 argparse 和 docopt 需要自行對決議后的引數來做路由關系,簡單了不少,
以下示例為 click 實作的 計算器程式:
import sys import click sys.argv = ['calculator.py', '--sum', '1', '2', '3'] # 2. 定義引數 @click.command() @click.argument('nums', nargs=-1, type=int) @click.option('--sum', 'use_sum', is_flag=True, help='sum the nums (default: find the max)') # 1. 業務邏輯 def calculator(nums, use_sum): """Calculator Program.""" print(nums, use_sum) # 輸出:(1, 2, 3) True if use_sum: result = sum(nums) else: result = max(nums) print(result) # 基于上文的 ['--sum', '1', '2', '3'] 引數,處理函式為 sum 函式,其結果為 6 calculator()
從上述示例可以看出,引數和對應的處理邏輯非常好地系結在了一起,看上去就很直觀,使得我們可以明確了解引數會怎么處理,這在有大量引數時顯得尤為重要,這邊是 click 相比于 argparse 和 docopt 最明顯的優勢,
此外,click 還內置了很多實用工具和額外能力,比如說 Bash 補全、顏色、分頁支持、進度條等諸多實用功能,可謂是如虎添翼,
2.4、fire
fire 則是用一種面向廣義物件的方式來玩轉命令列,這種物件可以是類、函式、字典、串列等,它更加靈活,也更加簡單,你都不需要定義引數型別,fire 會根據輸入和引數默認值來自動判斷,這無疑進一步簡化了實作程序,
以下示例為 click 實作的 計算器程式:
import sys import fire sys.argv = ['calculator.py', '1', '2', '3', '--sum'] builtin_sum = sum # 1. 業務邏輯 # sum=False,暗示它是一個選項引數 --sum,不提供的時候為 False # *nums 暗示它是一個能提供任意數量的位置引數 def calculator(sum=False, *nums): """Calculator Program.""" print(sum, nums) # 輸出:True (1, 2, 3) if sum: result = builtin_sum(nums) else: result = max(nums) print(result) # 基于上文的 ['1', '2', '3', '--sum'] 引數,處理函式為 sum 函式,其結果為 6 fire.Fire(calculator)
從上述示例可以看出,fire 提供的方式無疑是最簡單、并且最 Pythonic 的了,我們只需關注業務邏輯,而命令列引數的定義則和函式引數的定義融為了一體,
不過,有利自然也有弊,比如 nums 并沒有說是什么型別,也就意味著輸入字串'abc'也是合法的,這就意味著一個嚴格的命令列程式必須在自己的業務邏輯中來對期望的型別進行約束,
三、橫向對比
最后,我們橫向對比下argparse、docopt、click 和 fire 庫的各項功能和特點:
Python 的命令列庫種類繁多、各具特色,結合上面的總結,可以選擇出符合使用場景的庫,如果幾個庫都符合,那么就根據你更偏愛的風格來選擇,這些庫都很優秀,其背后的思想很是值得我們學習和擴展,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/53203.html
標籤:Python
