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


# まとめへのリンク

https://qiita.com/ganariya/items/fb3f38c2f4a35d1ee2e8

# はじめに

Pythonの勉強をするために、[acopy](https://github.com/rhgrant10/acopy)という群知能ライブラリを写経していました。

acopyでは、多くのPythonの面白い文法・イディオムが使われており、その中でも便利だなぁというのをまとめています。


今回は、動的に実行メソッドを変数名で指定する　ことに着目します。

# getattr

Pythonには`getattr`という組み込み関数が存在します。

`getattr(インスタンス, インスタンスのメソッド名)`で、インスタンスからそのメソッドを取り出すことができます。

とりあえず、簡単な例で試してみます。

```python

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の内部情報を利用したいユーザがプラグインを自作して、さらに機能拡張することができます。


実際にコードを見てみます。

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


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