最近因為需要在程式中埋下紀錄使用者過程中用到了那些 function,這樣方便日後追蹤重現結果。當然最簡單的方法是在每個 function 內都明確地用 logger 之類的寫進 log 裡,不過 python 有更簡單更不容易出錯的方式可以達到效果,因為用 logger 很容易在未來程式改版時有所疏漏。
方法基本上有三種:proxy, build-in __getattribute__(), decorator
proxy
說穿了就是另外包一層 proxy function/class,所有 function call 都會透過 proxy function/class 記錄。其實效果跟用 logger 差不多,差別是程式有改版的話不用擔心漏改,因為一定會出現不一致導致 runtime error。
https://stackoverflow.com/a/13770097
__getattribute__()
python class 內部不論是 property/member data 還是 function/method 其實都被視為一種 attribute,實際上的 function call 的過程中會拿使用者用到的名字丟進去 __getattribute__ 這個 build-in function,拿到真正的 handle 後才能去使用,因此我們其實可以重新實作 __getattribute__ 這支 function 來中途攔截所有的 funciton call
https://stackoverflow.com/a/4724111
class CommandWatcher(object):
def __init__(self):
self.cmd_list = []
# reimplement __getattribute__
def __getattribute__(self, name):
attr = super().__getattribute__(name)
if callable(attr):
def wrapped(*args, **kwargs):
arg = [str(v) for v in args] + [f"{k}={v}" for k, v in kwargs.items()]
cmd = f"{name}(" + ", ".join(arg) + ")"
self.cmd_list.append(cmd)
return attr(*args, **kwargs)
return wrapped
else:
return attr
decorator
利用 __getattribute__ 的方式雖然相當簡單明瞭,缺點是如果 function A 內用了 private function B,而我其實不希望 function B 也被記錄下來干擾日後判讀或重現,這時就會有困擾。其中一個方式是在 __getattribute__ 的過程中加上須滿足特定條件的 function 我才紀錄,例如 _ 開頭的 function 我就不紀錄;另一種方式就是這邊要使用的 decorator。唯有被加上 decorator 的 function 才會被記錄,缺點同於使用 logger,然而比 logger 更容易被使用是其優點。
class CommandWatcher(object):
def __init__(self):
self.cmd_list = []
# decorator usage
def record(func):
def wrapped(self, *args, **kwargs):
arg = [str(v) for v in args] + [f"{k}={v}" for k, v in kwargs.items()]
cmd = f"{func.__name__}(" + ", ".join(arg) + ")"
self.cmd_list.append(cmd)
return func(self, *args, **kwargs)
return wrapped
class MyClass(CommandWatcher):
def __init__(self):
super().__init__()
@CommandWatcher.record
def a(self, v):
print(f"a with {v}")
這邊需要特別注意的是 __getattribute__() 與 decorator 無法同時使用,因為 decorator 本質上是幫 function 重新包裝成 2nd order function,同時使用時反而會記錄到外層包裝過的 wrapper 而非我們真正想要的 function 本身。範例為了方便起見同時使用了兩者,然而實際使用時務必擇一使用。
完整範例如下:
沒有留言:
張貼留言