わずか 2 行のコードで Python でプラグイン アーキテクチャを実装します。
最近、私は Python コーディングを大量に行っていますが、プラグイン システムの実装がいかに簡単であるかが本当に気に入っています。
これはどのような場合に使うのでしょうか?コードを配布する場合、エンドユーザーがコードを拡張するためのフックを含めると良いでしょう。プラグインインターフェースをドキュメント化すれば、エンドユーザーは独自のカスタムコードを作成できます。
私の場合、Pythonのマルチプロセッシングモジュールを使って様々なジョブを実行しています。コアエンジン自体はあまり変更ありませんが、様々なジョブを作成しています。プラグインアーキテクチャを採用することで、エンジンを静的に保ち、プラグイン内で処理を実行できます。これはコードを分割するのに非常に効果的な方法です(ここでは詳しく説明しませんが、Pythonの例外処理を使えば、プラグインが壊れてもコードの残りの部分が壊れないようにすることができます)。
プラグインはとても使いやすいので、私は様々なコードで使っています。それでは、その使い方を説明しましょう。必要なのは(1) importlibモジュールと(2) 明確に定義されたインターフェースだけです。
今回は、シンプルな計算機を実装します。加算、減算、乗算、除算はそれぞれプラグインで行います(理論上はより多くの関数を追加して拡張できます)。これはサンプルなので、各プラグインは2つの数値に対して要求された演算を実行するだけです。また、エラーチェックと例外処理(例えば、プラグインをintではなく文字列で呼び出した場合や、引数が1つしかない場合など)はすべて省略します。
/25230 というディレクトリを作成し、その下に「plugins」というディレクトリを作成しました。そのディレクトリには「add.py」プラグインがあります。
#!/usr/bin/python3 クラスプラグイン: def calculate(self, *args): int(args[0]) + int(args[1]) を返す
非常にシンプルですが、重要なポイントが 2 つあります。
- Plugin クラスは、add.py、sub.py、multiply.py、divide.py で定義されているクラスと同じになります。これはすぐに理解できます。
- calculate() メソッドのシグネチャは各プラグインで同じです。これが実際にプラグインを実行する方法です。
sub.py などのコードは、calculate() の数学演算を除いて同一なので、それぞれを繰り返すつもりはありません。
それでは、これらのプラグインを使用する計算機 calc.py を見てみましょう。
#!/usr/bin/python3 plugin_dir = '/25230/plugins' importlib、os、re、sys をインポートする sys.path.append(plugin_dir) print ("計算機を起動します。 プラグインを読み込んでいます:") プラグイン = {} plugin_file を sorted(os.listdir(plugin_dir)) でソートした場合: re.search('\.py$', plugin_file): モジュール名 = re.sub('\.py$', '', プラグインファイル) モジュール = importlib.import_module(モジュール名) plugins[モジュール名] = module.Plugin() 印刷 (" %s" % ( モジュール名 )) print ("10 + 20 = %d" % plugins['add'].calculate(10,20) ) print ("18 - 11 = %d" % plugins['sub'].calculate(18, 11) ) print ("-3 * 6 = %d" % plugins['multiply'].calculate(-3, 6) ) print ("12 / 2 = %d" % plugins['divide'].calculate(12, 2) )
さて、「2行で実装する」と言いましたが、これは明らかに2行以上です。私は簡潔なコードよりも、より明確なコードを強く好みますが、どうしても必要な場合は、太字部分を2行に短縮することもできます。
glob.glob("%s/*.py" % ( plugin_dir )) の plugin_file の場合: プラグイン[ re.sub('\.py$', '', os.path.basename(plugin_file)) ] = importlib.import_module( re.sub('\.py$', '', os.path.basename(plugin_file))).Plugin()
行を折り返して収まるようにしました。でも、元のコードの方がずっと読みやすいと思います!もっと簡単に縮める方法があるかもしれません。私はPerlの古参なので、何でも正規表現を使うことが多いのですが、コンテキスト(「with」)やfilter()も使えます。
分かりました、分かりました。「import importlib」ステートメントと、プラグインファイルに書いたものもそうです。私はクリックベイトなタイトルを使っています。訴えてください。
しかし、話が逸れてしまいました。
出力は次のとおりです。
電卓を起動します。 プラグインを読み込んでいます: 追加 分ける 掛け算する サブ 10 + 20 = 30 18 - 11 = 7 -3 * 6 = -18 12 / 2 = 6
要点を見ていきましょう。
プラグイン = {}
私はプラグインをプラグイン名 -> プラグインモジュールの辞書に配置することを選択しました。
plugin_file を sorted(os.listdir(plugin_dir)) でソートした場合:
re.search('\.py$', plugin_file):
モジュール名 = re.sub('\.py$', '', プラグインファイル)
モジュール = importlib.import_module(モジュール名)
plugins[モジュール名] = module.Plugin()
print (" %s" % ( モジュール名 ))
これがコードの核心です。プラグインディレクトリ内を検索し、各ファイルについて正規表現(re.search)を使用して、末尾が「.py」であることを確認します(これにより、pycacheディレクトリや古いvimスワップファイルなどがスキップされます)。
各.pyファイル(例えば「add.py」)について、「.py」を削除してモジュール名を取得します。次に、そのモジュールを「module」という変数にインポートします。次の行では、そのモジュールからPlugin()クラスをインスタンス化し、plugins辞書に格納します。
次のようなこともできます:
plugins[モジュール名] = importlib.import_module(モジュール名) adder = plugins[モジュール名].Plugin() 合計 = adder.calculte(3, 5)
この2番目の例では、モジュールを辞書に格納していますが、私の実装では、インスタンス化されたプラグインオブジェクトを辞書に格納しています。プラグインのインスタンスを多数生成したい場合は、モジュールを格納する必要があります。
では、実際に実行してみましょう。各操作では、辞書を使ってPluginオブジェクトを検索し、そのcalculate()メソッドを呼び出します。
print ("10 + 20 = %d" % plugins['add'].calculate(10,20) )
print ("18 - 11 = %d" % plugins['sub'].calculate(18, 11) )
print ("-3 * 6 = %d" % plugins['multiply'].calculate(-3, 6) )
print ("12 / 2 = %d" % plugins['divide'].calculate(12, 2) )
たった数行のコードで、フル機能のプラグインシステムが完成します。ぜひお試しいただき、このアプローチがあなたのコードにどのような効果をもたらすかご確認ください。