动态代理对象是一种设计模式,允许在运行时动态地创建对象,并在这些对象上拦截和处理方法调用。它常用于 AOP(面向方面编程)、日志记录、权限控制等场景。应用非常广泛,下面跟着我来聊一聊我遇到的问题。
1、问题背景
在 IronPython 中,有时我们需要创建一个动态代理对象,以便在运行时动态呈现底层结构。这个代理对象本身不应该有任何函数和属性,我们希望捕获运行时中的所有调用。捕获函数调用很容易,我们只需要为对象定义一个 getattr() 函数,检查底层层中是否存在适当的函数,并返回一些类似函数的对象。但是,对于属性,我们不知道如何区分调用上下文,是作为左值还是右值来调用我的属性:
o = myproxy.myproperty # 我需要调用 underlying.myproperty_get()
或
myproxy.myproperty = o # 我需要调用 underlying.myproperty_set(o)
我们查看了 Python 中的特殊函数列表,但没有找到任何合适的方法。我们还尝试在对象中即时创建属性,结合使用 exec() 和内置的 property() 函数,但发现 IronPython 1.1.2 缺少整个 ‘new’ 模块(在 IronPython 2.x beta 中存在这个模块,但我们更喜欢使用 IP 1.x,因为它是 .NET 2.0 框架)。需要更多想法来解决这个问题。
2、解决方案
问题的解决方案是使用类来模拟代理行为,以下是代码示例:
class CallProxy(object):
'this class wraps a callable in an object'
def __init__(self, fun):
self.fun = fun
def __call__(self, *args, **kwargs):
return self.fun(*args, **kwargs)
class ObjProxy(object):
''' a proxy object intercepting attribute access
'''
def __init__(self, obj):
self.__dict__['_ObjProxy__obj'] = obj
def __getattr__(self, name):
attr = getattr(self.__obj, name)
if callable(attr):
return CallProxy(attr)
else:
return attr
def __setattr__(self, name, value):
setattr(self.__obj, name, value)
#keep a list of calls to the TestObj for verification
call_log = list()
class TestObj(object):
''' test object on which to prove
that the proxy implementation is correct
'''
def __init__(self):
#example attribute
self.a = 1
self._c = 3
def b(self):
'example method'
call_log.append('b')
return 2
def get_c(self):
call_log.append('get_c')
return self._c
def set_c(self, value):
call_log.append('set_c')
self._c = value
c = property(get_c, set_c, 'example property')
def verify(obj, a_val, b_val, c_val):
'testing of the usual object semantics'
assert obj.a == a_val
obj.a = a_val + 1
assert obj.a == a_val + 1
assert obj.b() == b_val
assert call_log[-1] == 'b'
assert obj.c == c_val
assert call_log[-1] == 'get_c'
obj.c = c_val + 1
assert call_log[-1] == 'set_c'
assert obj.c == c_val + 1
def test():
test = TestObj()
proxy = ObjProxy(test)
#check validity of the test
verify(test, 1, 2, 3)
#check proxy equivalent behavior
verify(proxy, 2, 2, 4)
#check that change is in the original object
verify(test, 3, 2, 5)
if __name__ == '__main__':
test()
这个解决方案使用了两个类:CallProxy 和 ObjProxy。CallProxy 类将一个可调用对象包装在一个对象中,以便在调用时执行这个可调用对象。ObjProxy 类代理对象拦截属性访问,并在访问时返回相应的属性或方法。
最后,我们使用一个测试函数来验证这个解决方案的正确性。测试函数创建一个 TestObj 对象,然后创建一个 ObjProxy 对象来代理 TestObj 对象。然后我们对代理对象进行各种操作,并验证代理对象的行为与 TestObj 对象的行为一致。
总的来说不管大家使用那种方法,最终还是需要更加我们实际情况来选择适合的才是最高效的。主要注意的是。这种方式在 IronPython 中实现了动态代理对象,可以灵活地拦截和处理方法调用。根据需要,可以在包装器函数中添加更多的逻辑,如日志记录、权限检查等。