アスペクト指向クラスを作成しました。
アスペクト指向のライブラリは数ありますが、
デファクトスタンダードがどれか解らず、
環境によってinstallに失敗してしまうという問題もあるので、
勉強がてら自作しました。
苦戦したポイントなどを含めて解説します。
アスペクト指向抽象クラス
まず、処理フローについて検討します。
アスペクト指向で行いたい処理は大体以下のようなものでしょう
- 関数実行前処理
- 関数実行後処理
- 例外発生時処理
これらの処理を抽象メソッドとして定義しておき、
サブクラスで実装を行うことで、多態性を持たせることが出来ます。
かかる抽象クラスはクラスデコレータとして扱います。
よって、__call__メソッド内にデコレータコール時の処理を書きます。
from abc import ABCMeta, abstractmethod class Aspect(metaclass=ABCMeta): """アスペクト指向抽象クラス""" @abstractmethod def pre_process(self, *args, **kwargs): pass @abstractmethod def after_process(self, result): pass @abstractmethod def raise_process(self, e: Exception): pass def __call__(self, func: object): # サブクラスから修飾対象メソッドの属性を取得できるように保持します self.func = func def wrapper(*args, **kwargs): self.pre_process(*args, **kwargs) try: result = func(*args, **kwargs) except Exception as e: self.raise_process(e) self.after_process(result) return result return wrapper
クラスデコレータには引数を受け取るものと、受け取らないものがあります。
上記は引数を受け取るクラスデコレータです。
引数を受け取るcallableクラスデコレータ
デコレータに渡されるパラメータをコンストラクタの仮引数とします。
__call__には修飾対象メソッドが渡され、
ローカル関数に修飾対象メソッドの実引数が渡されます。
利用側では、@decorator(引数)のように書きます。
引数を受け取らないcallableクラスデコレータ
コンストラクタに修飾対象メソッドが渡されます。
__call__に修飾対象メソッドの実引数が渡されます。
利用側では、@decoratorのように書きます。
当初は引数を受け取らないクラスデコレータとして作成しようとしたのですが、
selfの扱いに困り、引数を受け取るクラスデコレータを選択しました。
引数を受け取るクラスデコレータにおいては、
ローカル関数の可変長引数である*argsの第一要素に呼び出し元のselfが入ってくるという点がポイントです。
__call__内での修飾対象メソッド呼び出しは間接的な呼び出しとなるため、第一要素にselfが自動的に渡されるということがないようです。
selfが入ってこないと、修飾対象メソッド内でselfを利用できなくなります。
つまり修飾対象メソッドが所属しているクラスのインスタンス関数やインスタンス変数を一切使えなくなってしまいます。
そして、抽象クラス側から、呼び出し元クラスのselfを参照する手段も見つかりませんでした。
呼び出し元クラス側からselfが渡されることを期待するしかなかったのです。
よってローカル関数にselfを渡してくれる、引数を受け取るクラスデコレータの利用に至りました。
アスペクト指向サブクラス
サブクラスで抽象メソッドをオーバーライドし、ログ処理など、必要な処理を書いていきます。
処理が不要な関数に関してはpassと書けば、必要な処理だけを部分的に実装することが出来ます。
import aspects.aspect as aop class AOPPrinter(aop.Aspect): def pre_process(self, *args, **kwargs): from datetime import datetime print("START", self.func.__module__, self.func.__name__, datetime.now(), args, kwargs, sep='\t') def after_process(self, result): from datetime import datetime print("END", self.func.__module__, self.func.__name__, datetime.now(), result, sep='\t') def raise_process(self, e: Exception): from datetime import datetime print("RAISE", self.func.__module__, self.func.__name__, datetime.now(), e, sep='\t')
利用側
サブクラスをデコレータとして指定します。
引数ありデコレータとして扱うので末尾に括弧を書いています。
selfを要するインスタンスメソッドでも、selfを要しない静的なメソッドでも同様に修飾可能です。
@AOPPrinter() def static_method(): print("Execute") class TestAOPPrinter(): def self_method(self): self.self_value = "Self value" print(self.self_value) @AOPPrinter() def instance_method(self, a, b, name="", value=""): print("Execute") # selfが渡されないと以下のような参照が出来ません self.self_method() print(a, b, name, value) return a + b