Python Fire:自動生成命令行接口

命令行程序是平時寫一些小工具時最常用的方式。

爲了讓命令行程序更加靈活,我們常常會設置一些參數,根據參數讓程序執行不同的功能。
這樣就不用頻繁的修改代碼來執行不同的功能。

隨着命令行程序功能的豐富,也就是參數多了以後,解析和管理參數之間的關係會變得越來越繁重。
而本次介紹的 Fire 庫,正好可以解決這個問題。
使用 Fire 庫,我們只要關心具體功能的實現,最後Fire會幫助我們自動把所有功能組織成一個命令行程序。

Fire庫在github上的地址:https://github.com/google/python-fire

1. 一般命令

一般的命令,也就是帶有幾個參數的一段程序,比如:

# -*- coding:utf-8 -*-

def import_file(fp):
    print("import file from: {}".format(fp))

def export_file(fp):
    print("EXPORT file to: {}".format(fp))

這是模擬文件導出功能的兩個函數。

使用 Fire 轉換成命令行程序非常簡單,下面介紹幾種常用的方式。

1.1. 默認方式

# -*- coding:utf-8 -*-

import fire

def import_file(fp):
    print("IMPORT file from: {}".format(fp))

def export_file(fp):
    print("EXPORT file to: {}".format(fp))

if __name__ == "__main__":
    # fire默認會將所有函數轉換成子命令
    fire.Fire()

然後,就可以通過子命令的方式執行導入導出功能。

$ python main.py import_file --fp ./path/xxx.csv
IMPORT file from: ./path/xxx.csv

$ python main.py export_file --fp ./path/xxx.csv
EXPORT file to: ./path/xxx.csv

函數的名稱自動變爲子命令的名稱函數的參數自動變成子命令的參數

1.2. Fire<fn>

Fire庫的默認方式會把所有的函數都轉換爲子命令,
如果只想導出一個函數的話,可以用 Fire<fn>的方式。

if __name__ == "__main__":
    # 只導出 import_file 函數作爲子命令
    fire.Fire(import_file)

只導出一個函數的時候,執行命令的時候不需要輸入子命令的名稱。

$ python main.py --fp ./path/xxx.csv
IMPORT file from: ./path/xxx.csv

1.3. Fire<dict>

導出多個函數作爲子命令時,默認是使用函數名作爲子命令名稱的,函數名稱有時候會非常長,輸入很麻煩。
這時,可以用 Fire<dict> 方式。

if __name__ == "__main__":
    # 子命令的名稱分別是:import 和 export
    fire.Fire({
        "import": import_file,
        "export": export_file,
    })

執行時,使用簡化的子命令名稱。

$ python main.py import --fp ./path/xxx.csv
IMPORT file from: ./path/xxx.csv

$ python main.py export --fp ./path/xxx.csv
EXPORT file to: ./path/xxx.csv

這種方式非常靈活,不僅可以設置子命令名稱,還可以控制需要導出哪些函數。

1.4. Fire<object>

除了導出函數,Fire<object>方式也可以導出對象的公有方法。

import fire

class FileHandler():

    def __init__(self):
        pass

    def import_file(self, fp):
        print("IMPORT file from: {}".format(fp))

    def export_file(self, fp):
        print("EXPORT file to: {}".format(fp))

if __name__ == "__main__":
    fh = FileHandler()
    fire.Fire(fh)

使用方式如下:

$ python main.py import_file --fp ./path/xxx.csv
IMPORT file from: ./path/xxx.csv

$ python main.py export_file --fp ./path/xxx.csv
EXPORT file to: ./path/xxx.csv

使用對象的方式沒有直接使用函數那麼簡單,但有個好處是可以在初始化時傳入一些狀態。

import fire
import os

class FileHandler():

    def __init__(self, folder=""):
        self.folder = folder

    def import_file(self, fp):
        print("IMPORT file from: {}".format(os.path.join(self.folder, fp)))

    def export_file(self, fp):
        print("EXPORT file to: {}".format(os.path.join(self.folder, fp)))

if __name__ == "__main__":
    # 設置了默認文件夾,使用時直接傳入文件名即可
    fh = FileHandler("./default_path")
    fire.Fire(fh)
$ python main.py import_file --fp xxx.csv
IMPORT file from: ./default_path/xxx.csv

$ python main.py export_file --fp xxx.csv
EXPORT file to: ./default_path/xxx.csv

1.5. Fire<class>

Fire<class>的方式也可以直接作用在類上,不用初始化對象。

if __name__ == "__main__":
    fire.Fire(FileHandler)

Fire<object> 不同的是,__init__方法的參數也變成了命令的參數,也可以在命令行中調整了。

$ python main.py import_file --fp xxx.csv
IMPORT file from: xxx.csv

$ python main.py import_file --fp xxx.csv --folder ./my_folder
IMPORT file from: ./my_folder/xxx.csv

2. 組合命令

當功能越來越多時,可能就會需要組合一些功能一起運行,省得輸入一個一個的子命令。

class FileHandler():

    def __init__(self, folder="./defalut_dir"):
        self.folder = folder

    def import_file(self, fp):
        print("IMPORT file from: {}".format(os.path.join(self.folder, fp)))

    def export_file(self, fp):
        print("EXPORT file to: {}".format(os.path.join(self.folder, fp)))

class DatabaseHandler():

    def __init__(self, src="aliyun-mysql", dst="tecent-mysql"):
        self.src = src
        self.dst = dst

    def import_db(self):
        print("IMPORT data from: {} to: {}".format(self.src, self.dst))

    def export_db(self):
        print("EXPORT data from: {} to: {}".format(self.src, self.dst))

# 組合 FileHandler 和 DatabaseHandler
class ComposeHandler():
    def __init__(self):
        self.fh = FileHandler()
        self.dh = DatabaseHandler()

    def import_all(self, fp):
        self.fh.import_file(fp)
        self.dh.import_db()


    def export_all(self, fp):
        self.fh.export_file(fp)
        self.dh.export_db()

if __name__ == "__main__":
    fire.Fire(ComposeHandler)

導出組合命令之後,不僅可以執行組合命令,也可以只執行子命令。

$ python main.py import_all --fp xxx.csv
IMPORT file from: ./defalut_dir/xxx.csv
IMPORT data from: aliyun-mysql to: tecent-mysql

$ python main.py export_all --fp xxx.csv
EXPORT file to: ./defalut_dir/xxx.csv
EXPORT data from: aliyun-mysql to: tecent-mysql

$ python main.py fh export_file --fp xxx.csv
EXPORT file to: ./defalut_dir/xxx.csv

$ python main.py dh export_db
EXPORT data from: aliyun-mysql to: tecent-mysql

3. 鏈式命令

鏈式命令和組合命令不一樣的地方在於:
組合命令中,每個命令之間一般是相互獨立的,
而鏈式命令中,上一個命令的執行結果會對下一個命令造成影響。
比如:

class Stat():
    def __init__(self):
        self.total = 0
        self.avg = 0
        self.n = 0

    # 模擬統計合計值
    def sum(self, n):
        self.n += n
        for i in range(n):
            self.total += i

        return self

    # 模擬求平均值
    def average(self):
        if self.n == 0:
            self.avg = 0
        else:
            self.avg = self.total / self.n

        return self

    # 顯示分析結果
    def show(self):
        print("SUM: {}, and AVERAGE: {}".format(self.total, self.avg))

if __name__ == "__main__":
    fire.Fire(Stat)

執行鏈式命令時,可以先求和,再求平均值,最後顯示結果:

$ python main.py sum 10 average show
SUM: 45, and AVERAGE: 4.5

因爲是鏈式命令,所以可以多次執行:

$ python main.py sum 10 sum 10 average show
SUM: 90, and AVERAGE: 4.5

$ python main.py sum 10 sum 20 sum 30 average show
SUM: 670, and AVERAGE: 11.166666666666666

4. 複雜命令參數

上面的示例中,參數都是簡單的數據類型,比如字符串,數字之類的。

最後,介紹下複雜的命令參數如何使用,所謂複雜的參數,就是元組,列表,字典等等。

def hello(data):
    tp = type(data).__name__
    if  tp == "tuple" or tp == "list":
        for item in data:
            print("hello: {}".format(item))

    if tp == "dict":
        for k, v in data.items():
            print("hello: key {}, val {}".format(k, v))

if __name__ == "__main__":
    fire.Fire(hello)

python是弱類型語言,函數的參數可以是任何類型。
主要看看命令行中如何傳入複雜的類型:

$ python main.py "(aa, bb, cc)"
hello: aa
hello: bb
hello: cc

$ python main.py "[aa, bb, cc]"
hello: aa
hello: bb
hello: cc

$ python main.py "{aa: 11, bb: 22}"
hello: key aa, val 11
hello: key bb, val 22

5. 總結

PythonFire庫是一個構思非常巧妙的命令行接口庫,各種語言的命令行接口我接觸過不少,還沒有在其他編程語言中看到過類似的庫。

Fire庫最方便的地方在於,你不用再關心命令行的參數(參數的解析一直是命令行程序中最讓人頭疼的地方),只要專注於自己要實現的功能。
此外,如果你已經有一些python腳本的話,通過這個庫把它們改造成命令行程序也非常簡單。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章