1、装饰器概述
在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
实际上就是一个闭包,把一个函数当做函数参数传入,然后返回一个替代版函数,本质上就是一个返回函数的高阶函数
经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
装饰器的使用方法很固定
-
先定义一个装饰器(帽子)
-
再定义你的业务函数或者类(人)
-
最后把这装饰器(帽子)扣在这个函数(人)头上
语法
# 定义装饰器
def outer(func):
def inner():
# 添加要执行的功能
print("这个函数使用了装饰器")
return func()
return inner
@outer
def function():
print("hello,Decorator")
外函数的功能:
1.将被装饰的函数作为参数传进去
2.将定义的内函数的函数名返回
内函数:
1.执行要添加的功能
2.执行被装饰的函数
return的功能:结束函数,将结束的结果返回
function函数:
业务函数,使用装饰器功能的函数
function()
# 调用该函数,输出内容为:
'''
这个函数使用了装饰器
hello,Decorator
'''
实际上,装饰器并不是编码必须性,意思就是说,你不使用装饰器完全可以,它的出现,应该是使我们的代码
- 更加优雅,代码结构更加清晰
- 将实现特定的功能代码封装成装饰器,提高代码复用率,增强代码可读性
2、简单的装饰器
函数也可以是一个对象,而且函数对象可以被赋值给变量,所以通过变量也可以调用该函数
def now():
ptint("2018-3-18")
f = now
f()
函数对象有一个____name____
属性,可以拿到函数的名字:
def now():
print("2018-4-3")
f = now
f()
print(now.__name__)
print(f.__name__)
#结果
2018-4-3
'now'
'now'
现在我们要增强now()的函数功能,比如在函数调用前打印一段分隔符。
def log(func):
def wrapper():
print("**********")
return func()
return wrapper
def now():
print("2018-3-18")
#装饰器的调用
f = log(now)
f()
输出结果
**********
2018-3-18
这种方式就是通过log函数,将now函数作为参数传入,实现在now函数调用前打印一段分隔符。
3、复杂一点的装饰器
需求:输入年龄,并打印出来(年龄不能为负数)
def getAge(age):
print("Tom age is %d" % age)
getAge(10)
# 输出结果 Tom age is 10
# 调用getAge方法得到年龄,但是如果输入的是负数就会得到不符合常理的结果
getAge(-10)
# 输出结果 Tom age is 10
# 定义一个函数,作为装饰器使用,实现将负数年龄转化为0的功能
def wrapper(func):
def inner(num):
if num < 0:
num = 0
return func(num)
return inner
newGetAge = wrapper(getAge)
newGetAge(-10)
# 输出结果 Tom age is 0
通过这样的方式,我们的代码变得更加简洁,将边界检查的逻辑隔离到单独的方法中,然后通过装饰器包装的方式应用到我们需要进行检查的地方。
另外一种方式通过在计算方法的开始处和返回值之前调用边界检查的方法能够达到同样的目的,但是不可置否的是,使用装饰器能够让我们以最少的代码量达到坐标边界检查的目的。
4、带@标识符的装饰器
使用@标识符将装饰器应用到函数
python2.4及以上版本支持使用标识符@将装饰器应用在函数上,只需要的函数的定义之前上@和装饰器的名称
若被装饰函数存在参数的时候,装饰器的写法:
def outer(f):
def inner(参数列表):
#添加要执行的功能
return f(参数列表)
return inner
def logger(func):
def inner(*args,**kwargs):
print("***********")
return func(*args, **kwargs)
return inner
@logger
def myPrint():
print("you are very good")
myPrint()
比如在公司实际开发的过程中,如果有一个别人写好的函数,你想向其中添加某些功能或者修改某些功能,而人家要求你不能随意修改人家的函数,则这时就可采用装饰器,在不修改别人的源码的同时,还可以增加自己的功能。
练习:现有一个登陆函数,要求:给登陆函数添加一些限制,用户名5~8位全部字母,psd必须是六位全部是数字.
若不符合则提示用户"用户名或者密码非法",若符合再进行登陆处理
def loginouter(func):
def inner(user, psd):
if len(user) >= 5 and len(user) <= 8 and user.isalpha() and len(psd) == 6 and psd.isdigit():
return func(user, psd)
else:
print("该条用户名{}或密码{}输入不符合要求".format(user, psd))
return func(user, psd)
return inner
@loginouter
def login(user, psd):
if user == "admin" and psd == "123456":
print("用户名或密码正确")
else:
print("用户名或密码不正确")
login("admin", "123456")
# 用户名或密码正确
login("admin", "1234ee")
# 该条用户名admin或密码1234ee输入不符合要求
# 用户名或密码不正确
不带参数的函数装饰器
不带参数的函数装饰器就是上面所述的普通的装饰器
实现日志功能:
- 在函数执行前,先记录一行日志
- 在函数执行完,再记录一行日志,并统计函数执行消耗的时间
from datetime import datetime
import time
# 这是装饰器函数,参数 func 是被装饰的函数
def funcLogger(func):
def wrapper(*args, **kwargs):
start = datetime.now()
print('在{} {}函数开始执行'.format(start,func.__name__))
# 真正执行的是这行。
func(*args, **kwargs)
time.sleep(5)
end = datetime.now()
usedtime = end-start
print('在{} {}函数结束执行,消耗时间为{}ms'.format(end,func.__name__,usedtime.microseconds))
return wrapper
@funcLogger
def sumadd(x, y):
print('{} + {} = {}'.format(x, y, x+y))
sumadd(200, 50)
# 执行结果:
'''
在2021-01-07 15:38:34.089192 sumadd函数开始执行
200 + 50 = 250
在2021-01-07 15:38:39.089269 sumadd函数结束执行,消耗时间为0:00:05.000077
'''
带参数的函数装饰器
不传参的装饰器,只能对被装饰函数,执行固定逻辑。
装饰器本身是一个函数,做为一个函数,如果不能传参,那这个函数的功能就会很受限,只能执行固定的逻辑。
这意味着,如果装饰器的逻辑代码的执行需要根据不同场景进行调整,若不能传参的话,我们就要写两个装饰器,这显然是不合理的。
接着上面的内容,定义一个装饰器,且限制时间,如果函数执行时间超过10s,打印日志函数执行时间不正常
from datetime import datetime
import time
# 这是装饰器函数,参数 func 是被装饰的函数
def funcLogger(timeselect):
def wrapper(func):
def timeIsout(*args, **kwargs):
start = datetime.now()
print('在{} {}函数开始执行'.format(start,func.__name__))
# 真正执行的是这行。
func(*args, **kwargs)
time.sleep(5)
end = datetime.now()
usedtime = end-start
print('在{} {}函数结束执行,消耗时间为{}ms'.format(end,func.__name__,usedtime.microseconds))
if int(timeselect)<usedtime.seconds:
print('{}函数执行时间正常'.format(func.__name__))
else :
print('{}函数执行时间不正常'.format(func.__name__))
return func(*args, **kwargs)
return timeIsout
return wrapper
@funcLogger('10')
def sumadd1(x, y):
return ('{} + {} = {}'.format(x, y, x+y))
@funcLogger(timeselect=10)
def sumadd2(x, y):
return ('{} + {} = {}'.format(x, y, x+y))
print(sumadd1(200, 50))
'''
在2021-01-07 22:03:04.738600 sumadd函数开始执行
在2021-01-07 22:03:09.739227 sumadd函数结束执行,消耗时间为627ms
sumadd函数执行时间正常
200 + 50 = 250
'''
print(sumadd2(200, 50))
'''
在2021-01-07 22:03:28.388342 sumadd函数开始执行
在2021-01-07 22:03:33.389965 sumadd函数结束执行,消耗时间为1623ms
sumadd函数执行时间正常
200 + 50 = 250
'''
比如我们要实现一个可以定时发送邮件的任务(一分钟发送一封),定时进行时间同步的任务(一天同步一次),就可以自己实现一个 periodic_task (定时任务)的装饰器,这个装饰器可以接收一个时间间隔的参数,间隔多长时间执行一次任务。
@periodic_task(spacing=60)
def send_mail():
pass
@periodic_task(spacing=86400)
def ntp()
pass
可以这样像下面这样写,由于这个功能代码比较复杂,不利于学习,这里就不贴了。
不带参数的类装饰器
基于类装饰器的实现,必须实现 __call__ 和 __init__两个内置函数。
__init__ :接收被装饰函数
__call__ :实现装饰逻辑。
以日志打印这个简单的例子为例
from datetime import datetime
import time
class FuncLogger(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
start = datetime.now()
print('在{} {}函数开始执行'.format(start, self.func.__name__))
# # 真正执行的是这行。
# func(*args, **kwargs)
time.sleep(5)
end = datetime.now()
usedtime = end - start
print('在{} {}函数结束执行,消耗时间为{}ms'.format(end, self.func.__name__, usedtime.microseconds))
return self.func(*args, **kwargs)
@FuncLogger
def sumadd(x, y):
print('{} + {} = {}'.format(x, y, x+y))
sumadd(200, 50)
'''
执行结果
在2021-01-10 21:56:42.743549 sumadd函数开始执行
在2021-01-10 21:56:47.744193 sumadd函数结束执行,消耗时间为644ms
200 + 50 = 250
'''
带参数的类装饰器
上面不带参数的例子,你发现没有,只能打印INFO
级别的日志,正常情况下,我们还需要打印DEBUG
和WARNING
等级别的日志。这就需要给类装饰器传入参数,给这个函数指定级别了。
带参数和不带参数的类装饰器有很大的不同。
__init__ :不再接收被装饰函数,而是接收传入参数。
__call__ :接收被装饰函数,实现装饰逻辑。
from datetime import datetime
import time
class FuncLogger(object):
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func): # 接受函数
def wrapper(*args, **kwargs):
start = datetime.now()
print('[{level}]: 在{start} {func}函数开始执行'.format(level=self.level,start=start, func=func.__name__))
time.sleep(5)
end = datetime.now()
usedtime = end - start
print('[{level}]:在{end} {func}函数结束执行,消耗时间为{usedtime}ms'.format(level=self.level,end=end, func=func.__name__, usedtime=usedtime.microseconds))
return func(*args, **kwargs)
return wrapper # 返回函数
# 指定 为`WARNING` 级别
@FuncLogger(level='WARNING')
def sumadd(x, y):
print('{} + {} = {}'.format(x, y, x+y))
sumadd(200, 50)
'''
执行结果
[WARNING]: 在2021-01-10 22:13:07.990326 sumadd函数开始执行
[WARNING]:在2021-01-10 22:13:12.990961 sumadd函数结束执行,消耗时间为635ms
200 + 50 = 250
'''
使用偏函数与类实现装饰器
绝大多数装饰器都是基于函数和闭包实现的,但这并非制造装饰器的唯一方式。
事实上,Python 对某个对象是否能通过装饰器( @decorator
)形式使用只有一个要求:decorator 必须是一个“可被调用(callable)的对象。
对于这个 callable 对象,我们最熟悉的就是函数了。
除函数之外,类也可以是 callable 对象,只要实现了__call__
函数(上面几个例子已经接触过了)。
还有容易被人忽略的偏函数其实也是 callable 对象。
接下来就来说说,如何使用 类和偏函数结合实现一个与众不同的装饰器。
如下所示,DelayFunc 是一个实现了 __call__
的类,delay 返回一个偏函数,在这里 delay 就可以做为一个装饰器,用于推迟某个函数的执行。
import time
import functools
class DelayFunc:
def __init__(self, waittime, func):
self.waittime = waittime
self.func = func
def __call__(self, *args, **kwargs):
print(f'该函数等待{self.waittime} 秒再开始执行...')
time.sleep(self.waittime)
return self.func(*args, **kwargs)
def eager_call(self, *args, **kwargs):
print('立即执行,不调用delay推迟')
return self.func(*args, **kwargs)
def delay(waittime):
"""
装饰器:推迟某个函数的执行。
同时提供 .eager_call 方法立即执行
"""
# 此处为了避免定义额外函数,
# 直接使用 functools.partial 帮助构造 DelayFunc 实例
return functools.partial(DelayFunc, waittime)
@delay(waittime=2)
def sumadd(a, b):
return a+b
业务函数很简单,就是相加
@delay(waittime=2)
def sumadd(a, b):
return a+b
来看一下执行过程
print(sumadd)
'''
# 打印结果
<__main__.DelayFunc object at 0x000001ED30F7D9C8>
可见 sumadd 变成了 Delay 的实例
'''
print(sumadd.func)
'''
# 打印结果
<function sumadd at 0x000001B01470AF78>
可见sumadd 变成了 Delay 的实例后,实现实例方法
'''
print(sumadd(2,3))
'''
# 打印结果
该函数等待2 秒再开始执行...
5
直接调用实例,进入 __call__
'''
能装饰类的装饰器
用 Python 写单例模式的时候,常用的有三种写法。其中一种,是用装饰器来实现的。
装饰器版的单例写法:
instances = {}
def singleton(cls):
def get_instance(*args, **kw):
cls_name = cls.__name__
print('===== 1 ====')
if not cls_name in instances:
print('===== 2 ====')
instance = cls(*args, **kw)
instances[cls_name] = instance
return instances[cls_name]
return get_instance
@singleton
class User:
_instance = None
def __init__(self, name):
print('===== 3 ====')
self.name = name
可以看到我们用singleton 这个装饰函数来装饰 User 这个类。装饰器用在类上,并不是很常见,但只要熟悉装饰器的实现过程,就不难以实现对类的装饰。在上面这个例子中,装饰器就只是实现对类实例的生成的控制而已。
实例化的过程,参考调试过程,加以理解。