3 回答

TA貢獻1860條經驗 獲得超9個贊
我可以想到一個解決方案,它并不完美,但可能是一個開始。我們可以通過從裝飾類繼承的類中的__getattribute__和捕獲實例屬性訪問__setattribute__:
import re
dunder_pattern = re.compile("__.*__")
protected_pattern = re.compile("_.*")
def is_hidden(attr_name):
return dunder_pattern.match(attr_name) or protected_pattern.match(attr_name)
def attach_proxy(function=None):
function = function or (lambda *a: None)
def decorator(decorated_class):
class Proxy(decorated_class):
def __init__(self, *args, **kwargs):
function("init", args, kwargs)
super().__init__(*args, **kwargs)
def __getattribute__(self, name):
if not is_hidden(name):
function("acces", name)
return object.__getattribute__(self, name)
def __getattr__(self, name):
if not is_hidden(name):
function("acces*", name)
return object.__getattr__(self, name)
def __setattribute__(self, name, value):
if not is_hidden(name):
function("set", name, value)
return object.__setattribute__(self, name, value)
def __setattr__(self, name, value):
if not is_hidden(name):
function("set*", name, value)
return object.__setattr__(self, name, value)
return Proxy
return decorator
然后你可以用它來裝飾你的班級:
@attach_proxy(print)
class A:
x = 1
def __init__(self, y, msg="hello"):
self.y = y
@classmethod
def foo(cls):
print(cls.x)
def bar(self):
print(self.y)
這將導致以下結果:
>>> a = A(10, msg="test")
init (10,) {'msg': 'test'}
set* y 10
>>> a.bar()
acces bar
acces y
10
>>> a.foo() # access to x is not captured
acces foo
1
>>> y = a.y
acces y
>>> x = A.x # access to x is not captured
>>> a.y = 3e5
set* y 300000.0
問題:
不會捕獲類屬性訪問(為此需要一個元類,但我看不到即時執行的方法)。
TypeA是隱藏的(在 type 后面Proxy),這可能更容易解決:
>>> A
__main__.attach_proxy.<locals>.decorator.<locals>.Proxy
另一方面,這不一定是問題,因為這會按預期工作:
>>> a = A(10, msg="test")
>>> isinstance(a, A)
True
編輯請注意,我不會將實例傳遞給function調用,但這實際上是一個好主意,將調用替換為function("acces", name)to function("acces", self, name)。這將允許與您的裝飾者一起制作更多有趣的東西。

TA貢獻1780條經驗 獲得超4個贊
您可以使用猴子補丁來實現這一點。將對象上的成員函數之一重新分配為裝飾函數,該函數又調用原始函數,并添加了一些日志記錄。
例如:
a = Test() # An object you want to monitor
a.func() # A specific function of Test you want to decorate
# Your decorator
from functools import wraps
def addLogging(function):
@wraps(function)
def wrapper(*args, **kwargs):
print 'Calling {}'.format(function.func_name)
return function(*args, **kwargs)
return wrapper
a.func = addLogging(a.func)
但是請注意,猴子補丁最好僅用于單元測試,而不是生產代碼。它可能有不可預見的副作用,應謹慎使用。
至于識別成員變量的值何時發生變化,可以參考這個。
所有這些都需要您修改客戶端代碼——如果有一種方法可以在不改變客戶端代碼的情況下實現這一點,我不知道。

TA貢獻1853條經驗 獲得超9個贊
您可以將@suicideteddy 提供的答案與方法檢查結合起來,結果類似于以下內容:
# Your decorator
def add_logging(function):
@wraps(function)
def wrapper(*args, **kwargs):
print 'Calling {}'.format(function.func_name)
return function(*args, **kwargs)
return wrapper
instance = Test() # An object you want to monitor
# list of callables found in instance
methods_list = [
method_name for method_name in dir(instance) if callable(
getattr(instance, method_name)
)
]
# replaces original method by decorated one
for method_name in methods_list:
setattr(instance, method_name, add_logging(getattr(instance, method_name))
我還沒有測試過這個,但類似的東西應該可以完成工作,祝你好運!
添加回答
舉報