# coding: utf_8
"""forward_medhods.py
* 未定義メソッドの代理呼び出しをするクラス.
* Pythonのリフレクション機能を用いる.
* 220830: Created by Hiroki Arimura, arim@ist.hokudai.ac.jp
"""
import sys
# Example::
# import sys
# import backupcaller as bc
#
# class OtherObject:
# """転送先オブジェクトのクラス.
# ふつうのオブジェクトであり,特別な準備は不要.
# """
# def swim(self, name):
# print(f'\t@OtherObject: method="swim" is called')
# print(f'An animal {name} swims!')
# class MyObject:
# """転送元オブジェクトのクラス.
# """
# def __init__(self):
# """ここで,転送先オブジェクトを準備する."""
# self.child = OtherObject() #転送先オブジェクト
# ## メソッド転送
# def __getattr__(self, name):
# """メソッドが未定義のとき,呼び出される特殊関数.
# 未定義の属性呼び出しのときも呼ばれるので注意.
# """
# print(f'\t@MyObject: method="__getattr__" is called')
# return bc.BackupCaller(self.child, name, verbose=True)
# def walk(self, name):
# print(f'\t@OtherObject: method=swim is called')
# print(f'A cat {name} walk!')
# a_cat = MyObject()
# print('\n### EXP: Call a method "walk" defined on MyObject')
# a_cat.walk('a_cat')
# print('\n### EXP: Call a method "swim" not defined on MyObject, but defined on OtherObject')
# a_cat.swim('a_cat')
# print('\n### EXP: Call a method "donothing" not defined both on MyObject and OtherObject')
# a_cat.donothing('a_cat')
## メソッド転送
[docs]class BackupCaller():
"""メソッド呼び出しの転送をするクラス.
Args:
host (Object) : 呼び出しの転送先オブジェクト.
fname (str) : 呼び出しの関数名/メソッド名.
verbose (bool) : デバグ用出力のフラグ. default=False.
Notes:
具体的な利用法は,下記のExampleを参照されたい.次の手順で,メソッド転送が実行される.
* `MyObject`の初期化時に,自分が実装していないメソッドの転送先オブジェクトに,`OtherObject`を設定する.
* `MyObject`は,特殊関数 `__getattr__()`を次のように実装する:
+ もし関数名(属性名)`name`が自身に対して呼ばれたら,
+ 代理オブジェクト`agent`と関数名`name`を引数として,オブジェクト BackupCaller(self.agent, name)を生成して,それを返り値として返す.この時点では,元の呼び出しの引数`(a1,...,aN)`は渡されないことに注意.
* 呼び出し側では,返り値のBackupCallerオブジェクトを受け取り,python処理系が,それに元の呼び出しの引数`(a1,...,aN)`を与えて実行する.
Example::
import sys
import backupcaller as bc
class OtherObject:
#転送先オブジェクトのクラス.
#ふつうのオブジェクトであり,特別な準備は不要.
def swim(self, name):
print(f'\t@OtherObject: method="swim" is called')
print(f'An animal {name} swims!')
class MyObject:
#転送元オブジェクトのクラス.
def __init__(self, agent=None):
#ここで,転送先オブジェクトを準備する.#
if agent != None:
self.agent = agent #転送先オブジェクト
## メソッド転送
def __getattr__(self, name):
#メソッドが未定義のとき,呼び出される特殊関数.
#未定義の属性呼び出しのときも呼ばれるので注意.
print(f'\t@MyObject: method="__getattr__" is called')
return bc.BackupCaller(self.agent, name, verbose=True)
def walk(self, name):
print(f'\t@OtherObject: method=swim is called')
print(f'A cat {name} walk!')
#a duck can swim
a_duck = OtherObject()
#a cat can walk, but cannot swim
a_cat = MyObject(agent=a_duck)
a_cat.walk('a_cat') #a cat can walk
=> walk
a_cat.swim('a_cat') #Since a cat cannot swim, a duck swims instead
=> swim
a_cat.donothing('a_cat')
=> fail
"""
def __init__(self, host, fname, verbose=False, verbose_prefix=None):
"""元の呼び出し先のオブジェクト`host`と呼びされるメソッド名`fname`を受け取り,格納する.
"""
self.host = host
self.fname = fname
self.verbose = verbose
self.verbose_prefix = verbose_prefix
def __call__(self, *args, **kwargs):
"""このオブジェクトのメソッド呼び出しにおいて,常に呼び出される特殊関数.
* 引数`(arg1, ..., kwargs1, ...)` は,元のメソッド呼び出しの引数.
* タスクとして,自身に格納した元の呼び出しのオブジェクト`host`とメソッド名`fname`に対して,呼び出し`host.fname(arg1, ..., kwargs1, ...)`を実行する.
"""
if self.verbose_prefix:
prefix = self.verbose_prefix
else:
prefix = ''
if self.verbose: print(f'@BackupCaller: method="__call__" is called')
if hasattr(self.host, self.fname):
func = getattr(self.host, self.fname, None)
if self.verbose: print(f'@BackupCaller: found method={type(func)} in self.host={self.host}')
if self.verbose: print(f'@BackupCaller: exec method={type(func)} as "{self.fname}"')
func(*args, **kwargs)
# return self.host.func(self.fname, *args, **kwargs)
else:
print(f'error: {self.host} has no method {self.fname}!')
sys.exit(1)
## EOF