我能想到一个解决方案,它并不完美,但它可能是一个开始。我们可以通过 __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)。这将允许用你的装饰器做更多有趣的事情。