装饰器简介:
装饰器本质是一个函数或者类,外部传入被装饰函数名,内部返回装饰函数名,在不修改被装饰函数源代码的前提下,扩展它的新的处理功能。装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
备注:如果想更好的理解python装饰器,需要先掌握python闭包。
普通装饰器:
看下述示例:
import redis#连接redis数据库,用作缓存pool = redis.ConnectionPool(host='localhost',port=6379,decode_responses=True)r = redis.Redis(connection_pool=pool)def cache(func): def wrapper(): result = func() r.set(result["name"],result["job"]) #向数据库中插入数据 return wrapper@cachedef func(): name = "小樱" job = "UI设计师" return { "name":name,"job":job}func()find_name = "小樱"print("{}的工作是{}".format(find_name,r.get(find_name))) #从数据库中获取数据#运行结果:小樱的工作是UI设计师
上面的cache函数就是一个装饰器了,它对原函数做了包装并返回了另外一个函数,给原函数添加了将数据存入缓存的功能。但有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了,并且被装饰的函数可能多种多样,会有不同的参数。咋整?还好Python提供了可变参数*args
和关键字参数**kwargs
,有了这两个参数,装饰器就可以用于任意目标函数了,请看下述示例:
import redis#连接redis数据库,用作缓存pool = redis.ConnectionPool(host='localhost',port=6379,decode_responses=True)r = redis.Redis(connection_pool=pool)def cache(func): def wrapper(*args,**keargs): result = func(*args,**keargs) r.set(result["name"],result["job"]) #向数据库中插入数据 return wrapper@cachedef func(name,job): return { "name":name,"job":job}func("小李","Java工程师")find_name = "小李"print("{}的工作是{}".format(find_name,r.get(find_name))) #从数据库中获取数据#运行结果:小李的工作是Java工程师
带参数的装饰器:
假设我们前文的装饰器需要完成的功能不仅仅是将数据加入缓存,而且还需指定缓存保留的时间,那么装饰器就会是这样的:
import redis#连接redis数据库,用作缓存pool = redis.ConnectionPool(host='10.175.120.131',port=6379,decode_responses=True)r = redis.Redis(connection_pool=pool)def cache(ex): def wrapper(func): def inner_wrapper(*args,**keargs): result = func(*args,**keargs) r.set(result["name"],result["job"],ex=ex) #向数据库中插入数据,过期时间为ex=100秒 return inner_wrapper return wrapper@cache(ex = 100) #如果这里没有使用@语法糖,等同于:func = cache(ex = 100)(func)def func(name,job): return { "name":name,"job":job}func("小李","Java工程师")find_name = "小李"print("{}的工作是{}".format(find_name,r.get(find_name))) #从数据库中获取数据
如果熟悉闭包的相关知识的话,上述代码不难理解。@cache(ex = 100)实际可以理解为:运行 cache(ex = 100),返回wrapper函数,而wrapper函数实际就是个装饰器,并且夹带着ex参数,再运行wrapper(func),inner_wrapper函数就能接受ex参数,并返回inner_wrapper函数,便实现了给func函数添加数据缓存功能。
类装饰器:
在讲类装饰前,需要先了解python的内置函数——callable:
1.该方法用来检测对象是否可以被调用,可被调用指的是对象能否使用()括号的方法调用。
2.可调用对象(callable(object)返回为True)在实际调用时也可能失败,但不可调用对象(callable(object)返回为False),肯定会调用失败。
3.类对象都是可调用对象,类的实例对象是否可调用,取决于该类是否定义了 __call__方法。
示例如下:
>>> class A: pass>>> callable(A)True >>> a = A()>>> callable(a)False>>> a()Traceback (most recent call last): File "", line 1, in a()TypeError: 'A' object is not callable>>> class B: def __call__(self): print("hello") >>> callable(B)True>>> b = B()>>> callable(b)True>>> b()hello
了解了上述知识后,可以让类的构造函数 __init__()接受一个函数,然后重载 __call__(),并返回该函数,就可以达到装饰函数的效果,示例如下:
import timeclass Time(object): def __init__(self,func): self.func = func def __call__(self, *args, **kwargs): time_start = time.time() self.func(*args, **kwargs) time_end = time.time() print("运行时间为{}秒".format(int(time_end - time_start)))@Timedef func(x): time.sleep(x)func(1) #运行 func(1) 时,实际是先创建Time的实例 t = Time(func),再运行 t(1),而t(1)调用的是实例t的 __call__方法#运行结果:运行时间为1秒
带参数的类装饰器:
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__
方法是就需要接受一个函数并返回一个函数。示例如下:
import timeclass Time(object): def __init__(self,unit): self.unit = unit def __call__(self, func): def wrapper(*args,**kwargs): time_start = time.time() func(*args, **kwargs) time_end = time.time() if self.unit == "毫秒": run_time = int((time_end - time_start)*1000) if self.unit == "秒": run_time = int(time_end - time_start) print("运行时间为{}{}".format(run_time,self.unit)) return wrapper@Time(unit = "秒")def func(x): time.sleep(x)func(2) #运行 func(2) 时,实际是先创建Time的实例 t = Time(unit = "秒"),再运行 wrapper = t(func),而t(func)调用的是实例t的 __call__方法,最后运行wrapper(2)#运行结果:运行时间为2秒