為了開始編寫插件,我們要使用我編寫的開放源碼 Python 包 pathtool,這個庫使用生成器操作文件系統併產生一個文件對象。這個庫允許開發人員編寫自己的過濾器來擴展它,過濾器對文件對象做一些處理,然後返回結果。
實際的 Python 模塊代碼比較長,不適合在本文中給出,所以只介紹開發人員實際使用的 API 片段:
def path(fullpath, pattern="*", action=(lambda rec: print_rec(rec))): """This takes a path, a shell pattern, and an action callback This function uses the slower pathattr function which calculates checksums """ for rec in pathattr(fullpath): for new_record in match(pattern, rec): #applies filter action(new_record) #Applies lambda callback to generator object |
看一下這個示例,可以看出這個路徑函數有一個必需的路徑位置參數,還有一個可選的模式關鍵字參數和一個可選的動作關鍵字參數(稱為 lambda 回調函數)。路徑的默認回調函數僅僅輸出文件名。開發人員只需要執行 easy_install 命令。關於使用 easy_install 命令的信息參見 參考資料。然後執行以下命令導入這個模塊並調用函數:
from pathtool import path path("/tmp", pattern="*.mp3", action=(lambda rec: print_rec(rec))) |
注意:本文提供了 pathtool 的 源代碼。這個示例的關鍵點是使用 lambda。在 參考資料 中可以找到關於 lambda 的 Python 教程,但是簡單地說,lambda 是讓一個函數 “調用” 另一個函數的簡便方法。
編寫一個可插入的命令行工具
我們已經基本了解了如何使用這個包含回調函數的路徑操作庫,現在要編寫一個可以用插件擴展的命令行工具。先看一下完成後的版本,然後分析其組成部分:
#!/usr/bin/env python # encoding: utf-8 """ pathtool-cli.py 0.1 A commandline tool for walking a filesystem. Takes Action callback plugins in a plugin directory action=(lambda rec: print_rec(rec)) """ from pathtool import path import optparse import re import os import sys try: plugin_available = True from plugin import * from plugin import __all__ #note this is the registered plugin list except ImportError: plugin_available = False def path_controller(): descriptionMessage = """ A command line tool for walking a filesystem.\ Takes callback 'Action' functions as plugins.\ example: pathtool_cli /tmp print_path_ext """ p = optparse.OptionParser(description=descriptionMessage, prog='pathtool', version='pathtool 0.1.1', usage= '%prog [starting directory][action]') p.add_option('--pattern', '-p', help='Pattern Match Examples: *.txt, *.iso, music[0-5].mp3\ plain number defaults to * or match all. \ Uses UNIX standard wildcard syntax.', default='*') p.add_option('--list', '-l', action="store_true", help='lists available action plugins', default=False) options, arguments = p.parse_args() if options.list: try: print "Action Plugins Available:" if plugin_available: for p in __all__: print p finally: sys.exit(0) if len(arguments) == 2: fullpath = arguments[0] try: action_plugin = eval(arguments[1]) #note we expect the plugin author to write a method with our naming convention #path(fullpath,options.pattern,action=(lambda rec: move_to_tmp.plugin(rec))) path(fullpath, options.pattern,action=(lambda rec: action_plugin.plugin(rec))) except NameError: sys.stderr.write("Plugin Not Found") sys.exit(1) else: print p.print_help() def main(): path_controller() if __name__ == '__main__': main() |
運行這個示例會產生以下輸出:
# python pathtool_cli.py Usage: pathtool [starting directory][action] A command line tool for walking a filesystem. Takes callback 'Action' functions as plugins. example: pathtool_cli /tmp print_path_ext Options: --version show program's version number and exit -h, --help show this help message and exit -p PATTERN, --pattern=PATTERN Pattern Match Examples: *.txt, *.iso, music[0-5].mp3 plain number defaults to * or match all. Uses UNIX standard wildcard syntax. -l, --list lists available action plugins |
在這個命令的輸出中可以看到,這個工具需要一個完整路徑,然後是一個 “動作”。動作是開發人員創建的一個插件。我增加了一個命令行列表選項,讓這個命令行工具的用戶可以看到可用的插件。看一下它的輸出:
# python pathtool_cli.py -l Action Plugins Available: move_to_tmp print_file_path_ext |
即使不太了解這個工具的工作原理,也能夠通過動作的名稱猜出它會執行哪些操作。我編寫的 print_file_path_ext 動作僅僅輸出路徑、文件名和擴展名,運行它,看看它的輸出:
# python pathtool_cli.py /tmp print_file_path_ext /tmp/foo0.txt | foo0.txt | .txt /tmp/foo1.txt | foo1.txt | .txt /tmp/foo10.txt | foo10.txt | .txt /tmp/foo2.txt | foo2.txt | .txt /tmp/foo3.txt | foo3.txt | .txt /tmp/foo4.txt | foo4.txt | .txt /tmp/foo5.txt | foo5.txt | .txt /tmp/foo6.txt | foo6.txt | .txt /tmp/foo7.txt | foo7.txt | .txt /tmp/foo8.txt | foo8.txt | .txt /tmp/foo9.txt | foo9.txt | .txt |
我使用 touch foo{0..10}.txt 創建了十一個臨時文件,現在這個命令行工具使用它找到的一個插件顯示完整路徑、文件名和擴展名(以 “|” 字元分隔)。
簡單的插件體系結構
到目前為止,我只討論了如何使用這個工具,還沒有解釋這些插件的工作原理。先看看這個模塊頂部的導入語句:
plugin_available = True from plugin import * from plugin import __all__ #note this is the registered plugin list except ImportError: plugin_available = False |
這個導入語句揭示了這個極其簡單的插件體系結構的秘密。一般情況下,Python 官方文檔不鼓勵使用 “from package import *” 語法,但是如果有合理理由的話(比如編寫插件),可以這樣做。插件作者負責在插件目錄中的 __init__.py 文件中創建一個條目。這個條目應該像下面這樣:
"""Lists all of the importable plugins""" __all__ = ["move_to_tmp", "print_file_path_ext"] |
通過創建這個條目,可以以 * 的形式導入包(或目錄)中的所有模塊。接下來,導入實際的 __all__ 列表,向用戶顯示可用的插件。最後,還需要一行代碼。因為直到運行之前命令行工具並不知道要使用哪個插件動作,所以要使用 eval 把命令行上的動作字元串轉換為一個可調用的函數,如下所示:
action_plugin = eval(arguments[1]) |
在一般情況下,應該極其謹慎地使用 eval,但是在這裡通過 eval 告訴工具使用哪些插件方法是合理的。
插件示例分析
既然已經了解了這個插件體系結構的工作原理,就來看看實際的插件。注意,為了讓這個體系結構發揮作用,需要在當前工作目錄或 Python site-packages 目錄中創建一個插件目錄。我們要討論的插件稱為 print_file_path_ext.py,它包含一個稱為 plug-in 的方法。這是插件開發人員必須滿足的 API 要求。
#!/usr/bin/env python # encoding: utf-8 """ prints path, name, ext, plugin """ def plugin(rec, verbose=True): """Moves matched files to tmp directory""" path = rec["path"] filename = rec["filename"] ext = rec["ext"] print "%s | %s | %s" % (path, filename, ext) |
這個插件非常簡單。它有一個 rec 參數,這個參數是 pathtool 模塊生成的詞典。這個詞典包含以下 API:
{"path": path, "filename": file, "ext": ext, "size": size, "unique_id": unique_id, "mtime": mtime, "ctime": ctime} |
在這個示例中,每當調用它時,使用詞典的鍵輸出特定文件對象的值。插件作者可以編寫許多更有用的動作,比如對文件進行轉換、重命名、存檔等等。
結束語
本文介紹了一個非常簡單的插件體系結構,可以通過它用 Python 擴展命令行工具。但是,應該注意幾點。首先,可以通過 easy_install 使用一個更高級的插件系統(參見參考資料)。這個插件系統允許用戶創建 “入口點” 來為工具定義插件。第二,我們的命令行工具只允許一個 “動作” 插件。可以修改這個命令行工具,讓它能夠接受數量不限的 “鏈式” 回調動作,這留給讀者作為練習。
關於創建鏈式插件還有一個問題:設計必須考慮到使用的 API 的性質。在我們的示例中,以生成器作為基礎。為了讓這個工具能夠把插件 “鏈接” 在一起,各個插件必須完成本身的工作,然後生成詞典記錄。我希望本文能夠鼓勵您為命令行工具編寫自己的插件。 (責任編輯:A6)
[火星人 ] 用 Python 編寫插件已經有574次圍觀