LoginSignup
3
3

More than 3 years have passed since last update.

【Python中級者への道】動的に実行メソッドを変数名で指定する

Posted at

まとめへのリンク

はじめに

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によって、記述量を減らすことができる

メリットが存在します。
ただし、実行するまで正しいかわからないため、実行時エラーの危険度は高まることは注意してください。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3