まとめへのリンク
はじめに
Pythonの勉強をするために、acopyという群知能ライブラリを写経していました。
acopyでは、多くのPythonの面白い文法・イディオムが使われており、その中でも便利だなぁというのをまとめています。
今回は、動的に実行メソッドを変数名で指定する ことに着目します。
getattr
Pythonにはgetattr
という組み込み関数が存在します。
getattr(インスタンス, インスタンスのメソッド名)
で、インスタンスからそのメソッドを取り出すことができます。
とりあえず、簡単な例で試してみます。
class A:
def __init__(self, msg):
self.msg = msg
def hello(self):
print("hello ", self.msg)
def add(self, x, y):
return x + y
'''
hello world
7
'''
a = A("world")
getattr(a, 'hello')()
print(getattr(a, 'add')(2, 5))
以上のような簡単なソースコードを書いてみます。
インスタンスaは、クラスAから生成されたもので
getattr(a, 'hello')
でインスタンスaのhello属性のメソッドを取得しています。
そして、その後ろで()をつけることで、取り出したメソッドを実際に実行しています。
応用編
getattrの実際の応用を見ていきます。
今回あなたはLibraryというクラスライブラリを作ることにしました。(直球)
ライブラリではなにか特有の処理をrunという関数で実行し、内部情報はとりあえずlibrary_information
というメンバ変数にもたせておきます。
ここで、あなたは、Libraryを使いたい他のユーザ用に、Libraryの内部情報にアクセスできるようなプラグインも配布することにしました。
あなたが作ったLibraryのため、あなたしかメイン処理は実装できませんが、run関数内にプラグインを実行する処理を用意することで、Libraryの内部情報を利用したいユーザがプラグインを自作して、さらに機能拡張することができます。
実際にコードを見てみます。
class Library:
def __init__(self):
self.plugins = []
self.library_information = {
'a': 10,
'b': 20,
'c': 30
}
def run(self):
# plugin start
self.call_plugins('start')
for cycle in range(3):
print("Library main process (実装は省略)")
# plugin iteration
self.call_plugins('iteration')
# plugin finish
self.call_plugins('finish')
def call_plugins(self, hook):
for plugin in self.plugins:
plugin(hook, **self.library_information)
def add_plugin(self, plugin):
self.plugins.append(plugin)
class Plugin:
def __init__(self):
pass
def __call__(self, hook, **kwargs):
getattr(self, f'on_{hook}')(**kwargs)
def on_start(self, **kwargs):
print(kwargs['a'])
def on_iteration(self, **kwargs):
print(kwargs['b'])
def on_finish(self, **kwargs):
print(kwargs['c'])
library = Library()
library.add_plugin(Plugin())
library.run()
'''
10
Library main process (実装は省略)
20
Library main process (実装は省略)
20
Library main process (実装は省略)
20
30
'''
Libraryクラス
Libraryは、あなたが自作してPyPIなどで公開したいライブラリです。
例えば、Numpy・Matplotlibなどを意識してください。
Libraryの内部情報にlibrary_information
というものを用意し、これを第三者のユーザが利用できるように・拡張できるようにしたいと考えました。
そこで、プラグイン配列をメンバ変数として用意し、Pluginクラスを継承してadd_pluginに渡せば、プラグインを実行できるようにしました。
LibraryからPluginの実行時に、Libraryの内部情報を渡すため、ユーザは機能拡張が行えます。
Pluginクラス
今回はprintで実装していますが、本来は抽象クラスでこのPluginもあなたが作成し、ユーザがこのPluginをさらに継承することでオリジナルプラグインを作成します。
__call__
関数を用意することで、関数のように実行できるようにします。
__call__
を呼び出す時、getattrを使用します。
これによって、Library側は、run内でPluginインスタンスを実行し、そのときにLibraryの情報を渡します。
そして、Pluginインスタンスはgetattrで、run内の適切なstart, finish, iteration
のhookに合う関数を持ち出し、それを利用します。
関数run内で、call_plugins_start, call_plugins_finishのようにgetattrも消去して、すべて場合分けして書くことはできますが、これによって、統一的に書くことができます。
ライブラリの拡張性
プラグインクラスをライブラリと一緒に用意し、ユーザに配ることで
- ライブラリ側からプラグインにライブラリ内の情報を渡す
- プラグインを継承して、ユーザが自作することで、ライブラリをプラグインで拡張て着る
- getattrによって、記述量を減らすことができる
メリットが存在します。
ただし、実行するまで正しいかわからないため、実行時エラーの危険度は高まることは注意してください。