有没有一种方法可以自动检测何时使用了python对象(并且可能对此做出反应)?
例如,假设我有一个Foo
类型的对象。我没有为Foo
编写类代码,因为它来自外部库。
我想以一种这样的方式“装饰”我的对象:无论何时使用它的一种方法,或者只要它的内部状态(成员)发生变化或被访问,我都会得到一些日志信息,例如{{1} }。
我使用“装饰”一词来强调我不想更改使用"Foo is being used"
类型对象的所有接口。我只是想为其添加一些功能。
我也避免了直接修改Foo
的类代码的方法,即通过在每个方法的开头显式添加Foo
语句(这两种方法都不会通知我)成员何时更改)。
我不想将我的对象显式注册到其他对象,因为那是一种“侵入式”方法,需要更改“客户端”代码(使用{{1}的代码}个对象),而这很容易被遗忘。
答案 0 :(得分:1)
您可以使用monkey patching来实现。将对象的成员函数之一重新分配为装饰函数,该函数继而调用原始函数并添加一些日志记录。
例如:
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)
但是,请注意,猴子修补最好仅用于单元测试,而不用于生产代码。它可能有无法预料的副作用,应谨慎使用。
关于确定成员变量的值何时更改的信息,可以参考this。
所有这一切都需要您修改客户端代码-如果有一种方法可以在不更改客户端代码的情况下实现此目的,我不知道。
答案 1 :(得分:1)
我可以想到一个解决方案,它虽然不完美,但可能只是一个开始。我们可以在将从装饰的类继承的类中通过__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
问题:
没有捕获类属性访问(为此需要一个元类,但我看不到一种动态的方法)。
类型A
被隐藏(在类型Proxy
之后),这可能更容易解决:
>>> A
__main__.attach_proxy.<locals>.decorator.<locals>.Proxy
另一方面,这不一定是问题,因为它将按预期工作:
>>> a = A(10, msg="test")
>>> isinstance(a, A)
True
编辑请注意,我不会将实例传递给function
调用,但这实际上是一个好主意,将调用从function("acces", name)
替换为function("acces", self, name)
。这将使您的装饰器制作出更多有趣的东西。
答案 2 :(得分:0)
您可以将@suicidalteddy提供的答案与方法检查结合起来,得到类似于以下内容:
# 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))
我还没有测试过,但是类似的东西应该可以工作,祝您好运!