以最少侵入性和最隐蔽的方式检测对象使用情况

有没有办法自动检测何时使用 python 对象(并可能对此做出反应)?

例如,假设我有一个类型为 的对象Foo。我没有为 编写类代码Foo,因为它来自外部库。

我想以这样的方式“装饰”我的对象,每当使用它的方法之一,或者每当它的内部状态(成员)发生变化或被访问时,我都会得到一些日志信息,比如"Foo is being used".

我使用“装饰”一词来强调我不想更改Foo使用类型对象的所有接口。我只想为它添加一些功能。

此外,我将避免Foo直接修改的类代码,即通过print在其每个方法的开头显式添加一条语句(无论哪种方式都不会通知我其成员何时发生变化)。

而且我不想将我的对象显式注册到其他一些对象,因为这将是一种“侵入性”方法,需要更改“客户端”代码(使用Foo对象的代码),这将是这很容易被遗忘。


DIEA
浏览 139回答 3
3回答

慕码人2483693

我可以想到一个解决方案,它并不完美,但可能是一个开始。我们可以通过从装饰类继承的类中的__getattribute__和捕获实例属性访问__setattribute__:import redunder_pattern = re.compile("__.*__")protected_pattern = re.compile("_.*")def is_hidden(attr_name):&nbsp; &nbsp; return dunder_pattern.match(attr_name) or protected_pattern.match(attr_name)def attach_proxy(function=None):&nbsp; &nbsp; function = function or (lambda *a: None)&nbsp; &nbsp; def decorator(decorated_class):&nbsp; &nbsp; &nbsp; &nbsp; class Proxy(decorated_class):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; def __init__(self, *args, **kwargs):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; function("init", args, kwargs)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; super().__init__(*args, **kwargs)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; def __getattribute__(self, name):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if not is_hidden(name):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; function("acces", name)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return object.__getattribute__(self, name)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; def __getattr__(self, name):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if not is_hidden(name):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; function("acces*", name)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return object.__getattr__(self, name)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; def __setattribute__(self, name, value):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if not is_hidden(name):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; function("set", name, value)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return object.__setattribute__(self, name, value)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; def __setattr__(self, name, value):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if not is_hidden(name):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; function("set*", name, value)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return object.__setattr__(self, name, value)&nbsp; &nbsp; &nbsp; &nbsp; return Proxy&nbsp; &nbsp; return decorator然后你可以用它来装饰你的班级:@attach_proxy(print)class A:&nbsp; &nbsp; x = 1&nbsp; &nbsp; def __init__(self, y, msg="hello"):&nbsp; &nbsp; &nbsp; &nbsp; self.y = y&nbsp; &nbsp; @classmethod&nbsp; &nbsp; def foo(cls):&nbsp; &nbsp; &nbsp; &nbsp; print(cls.x)&nbsp; &nbsp; def bar(self):&nbsp; &nbsp; &nbsp; &nbsp; print(self.y)这将导致以下结果:>>> a = A(10, msg="test")init (10,) {'msg': 'test'}set* y 10>>> a.bar()acces baracces y10>>> a.foo() # access to x is not capturedacces foo1>>> y = a.yacces y>>> x = A.x # access to x is not captured>>> a.y = 3e5set* 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)。这将允许与您的装饰者一起制作更多有趣的东西。

Helenr

您可以使用猴子补丁来实现这一点。将对象上的成员函数之一重新分配为装饰函数,该函数又调用原始函数,并添加了一些日志记录。例如:a = Test() # An object you want to monitora.func() # A specific function of Test you want to decorate# Your decoratorfrom functools import wrapsdef addLogging(function):&nbsp; &nbsp; @wraps(function)&nbsp; &nbsp; def wrapper(*args, **kwargs):&nbsp; &nbsp; &nbsp; &nbsp; print 'Calling {}'.format(function.func_name)&nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; return function(*args, **kwargs)&nbsp; &nbsp; return wrappera.func = addLogging(a.func)但是请注意,猴子补丁最好仅用于单元测试,而不是生产代码。它可能有不可预见的副作用,应谨慎使用。至于识别成员变量的值何时发生变化,可以参考这个。所有这些都需要您修改客户端代码——如果有一种方法可以在不改变客户端代码的情况下实现这一点,我不知道。

暮色呼如

您可以将@suicideteddy 提供的答案与方法检查结合起来,结果类似于以下内容:# Your decoratordef add_logging(function):&nbsp; &nbsp; @wraps(function)&nbsp; &nbsp; def wrapper(*args, **kwargs):&nbsp; &nbsp; &nbsp; &nbsp; print 'Calling {}'.format(function.func_name)&nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; return function(*args, **kwargs)&nbsp; &nbsp; return wrapperinstance = Test() # An object you want to monitor# list of callables found in instancemethods_list = [&nbsp; &nbsp;method_name for method_name in dir(instance) if callable(&nbsp; &nbsp; &nbsp; getattr(instance, method_name)&nbsp; &nbsp;)]# replaces original method by decorated onefor method_name in methods_list:&nbsp; &nbsp; setattr(instance, method_name, add_logging(getattr(instance, method_name))我还没有测试过这个,但类似的东西应该可以完成工作,祝你好运!
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python