Я могу придумать одно решение, оно не идеальное, но, вероятно, это начало. Мы можем получить доступ к атрибутам экземпляра через __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
Edit обратите внимание, что я не передаю экземпляры вызовам function
, но это было бы хорошей идеей, заменяя вызовы с function("acces", name)
на function("acces", self, name)
. Это позволило бы делать намного больше забавных вещей с вашим декоратором.