最近有个以前培训班的学员问我Python有没有类似与Java动态代理的机制?他想通过类似Java动态代理的机制对类的所有方法调用日志功能。查了一下,虽然Python官方没有提供这个功能,但是作为一门提供了反射机制的动态语言,我们妥妥的可以自己撸一个啊。
如果再阅读本文有什么不清楚的地方,建议先阅读廖雪峰老师教程的以下章节:
首先我们复习一下装饰器的实现,装饰器本质是一个高阶函数,通过封装对原油函数的调用,增强了原有函数的功能。
def log(func):
def wrapper(*args, **kws):
print('Call %s' % func.__name__)
ret = func(*args, **kws)
print('Finish call %s' % func.__name__)
return ret
return wrapper
@log
def add(x, y):
return x + y
z = add(1, 2)
print(z)
'''
输出如下:
Call add
Finish call add
3
'''
以上例子我们是通过装饰器增强了一个函数,那么如何通过装饰器增强一个类的所有方法呢?在展开具体实现之前,我们先介绍一下以下两个知识点:
class Sample:
def __init__(self, x, y):
self.x = x
self.y = y
def __call__(self):
return self.x + self.y
s = Sample(100, 200)
print(s())
'''
输出如下:
300
'''
class Wrapper:
def __init__(self, obj):
self.obj = obj
def __getattr__(self, attr):
ret = None # 可以找不到返回None,也可以抛出异常。
if hasattr(self.obj, attr):
ret = getattr(self.obj, attr)
return ret
class Sample:
def __init__(self, x, y):
self.x = x
self.y = y
def add(self):
return self.x + self.y
def minus(self):
return self.x - self.y
w = Wrapper(Sample(100, 200))
print(w.x)
print(w.y)
print(w.add())
print(w.minus())
'''
输出如下:
100
200
300
-100
'''
上面准备知识介绍完毕,下面我们就要展开具体实现了。
class LogProxy:
def __init__(self, obj, method):
self.obj = obj
self.method = method
def __call__(self, *args, **kws):
print('Log: ' + str(self.obj) + ' call ' + self.method.__name__)
ret = self.method(*args, **kws)
print('Log: ' + str(self.obj) + ' call ' + self.method.__name__ + ' finished')
import types
class Proxy:
def __init__(self, clz, pclz):
self.clz = clz
self.pclz = pclz
self.proxies = {} # 查表提高效率
def __call__(self, *args, **kws):
self.obj = self.clz(*args, **kws) # 调用被代理类的构造函数创建实例
return self
def __getattr__(self, attr): # 调用方法之前需要通过getattr查找方法
ret = None
if hasattr(self.obj, attr): # 查看被代理实例成员是否存在
ret = getattr(self.obj, attr)
if isinstance(ret, types.MethodType): # 如果该成员是方法
if ret not in self.proxies: # 如果该方法的代理没有被生成
self.proxies[ret] = self.pclz(self.obj, ret) # 创建该方法的代理
return self.proxies[ret]
return ret
class ProxyFactory:
def __init__(self, pclz):
self.pclz = pclz
def __call__(self, clz):
return Proxy(clz, self.pclz)
@ProxyFactory(LogProxy)
class Sample:
def __init__(self, name):
self.name = name
def print_name(self):
print(self.name)
if __name__ == '__main__':
s = Sample('xxx')
s.print_name()
print('s.name: %s' % s.name)
'''
输出如下:
Log: <__main__.Sample object at 0x7f8edd6020b8> call print_name
xxx
Log: <__main__.Sample object at 0x7f8edd6020b8> call print_name finished
s.name: xxx
'''
现在我们来解释一下代码的执行顺序,之后可以自己加日志跟踪一下代码是如何执行的。