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条经验 获得超3个赞
您可以使用猴子补丁来实现这一点。将对象上的成员函数之一重新分配为装饰函数,该函数又调用原始函数,并添加了一些日志记录。
例如:
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))
我还没有测试过这个,但类似的东西应该可以完成工作,祝你好运!
添加回答
举报