初学者如何理解装饰器
刚开始学习python,看到装饰器就头大,看了解释也是懵懵懂懂,如何更好理解装饰器,并应用它呢?
函数装饰器,就是给函数增加额外功能。举例:
如果我们想知道一个函数func
运行耗时,可以这么写:
import time
start = time.time()
res = func(*args, **kwargs)
elapse = time.time() - start
print('函数运行耗时:%s' % elapse)
而如果使用装饰器,就可以把这些都包装在一块,使用的时候@deco
语法糖的方式,就可以了。
import time
def deco(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
elapse = time.time() - start
print('函数运行耗时:%s' % elapse)
return res
return wrapper
@deco
def func(*args, **kwargs):
print('执行函数')
是不是感觉代码变多了?有点头大,尤其是装饰器这块,初步了解的人就很抗拒。分解来看,实际上我们就是加了个壳:
def deco(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
上边是常用的装饰器模式,把这个壳理解了,剩余的事,思路就是正常写功能代码(新增功能),然后套入到这个壳就行,就可以写出装饰器。我们理解一些问题,然后练习下,可能就会对装饰器有所明白了。
装饰器如何调用?
@deco
def func(*args, **kwargs):
print('执行函数')
def func(*args, **kwargs):
print('执行函数')
func = deco(func)
上边等价下边,函数func作为参数传入到装饰器中,然后返回函数func
(这是可调用对象),说明装饰器deco返回的是一个可调用对象,可调用就是指可以使用()
,所以上边装饰器deco返回的是wrapper
,也就是里层的函数,它是一个可调用对象
装饰器何时执行?
导入包的时候,就会立马执行,哪怕没有调用函数,也会执行装饰器。
所以如果我们编写装饰器的时候,功能代码如果写在内层函数的外边,就会立马执行,无论有没有调用函数。
def deco_exe(func):
print('这是装饰器外层1')
def wrapper(*args, **kwargs):
print('这是装饰器里层2')
return func(*args, **kwargs)
return wrapper
@deco_exe
def add_value(param):
print('函数执行3')
return param
直接运行上边代码,结果如下
这是装饰器外层1
为何需要两层嵌套?
简单记忆方式,外层函数传func
,内层函数传*args, **kwargs
,如果少了一层,就少传一个,如果装饰器还需要传参,那么还要再加一层外层来接收装饰器参数。
如果装饰器只有一层,就会立马执行代码,哪怕没有调用函数,这可能不是我们想要的,如果是这样,可以直接写代码即可,无需加装饰器了。当然,一层也可以作为装饰器。
为何需要*args, **kwargs
,可不可以写具体的参数名?
可以的,你可以在内层函数wapper
中,编写被装饰函数func
的形参名称,如下:
def deco_args(func):
def wrapper(param):
print('装饰器使用函数参数名')
return func(param)
return wrapper
@deco_args
def post_task(param):
return param + 1
但是这样做,有一个不好的地方,就是如果我这个装饰器,用来修饰多个函数,而每一个函数的参数个数和类型可能是不一样的,那这时,使用*args, **kwargs
就可以做到通用的模式,即使函数被修改参数了,也一样适用。
装饰器内层函数wrapper
能否不传参?
可以的,如果不传参,那么被装饰的函数func
也需要是一个无参函数,如果传入了参数,就会报错
装饰器的应用练习?
初学者光说不练,是很难理解装饰器的,代码敲起来,不要搬砖,可以自己看题,自己想一遍,自己写,写不出来时,再看别人写的,反正我是这么来的,蛮有效果。
# -*- coding:utf-8 -*-
"""
1、编写缓存,将每次读出的数据写入到txt文件中,如果文件大于290B,则清空文件,重新往里写入
2、记录函数执行时间,写入日志
3、记录函数异常,写入日志
4、超时重试,超过5秒则重试,最多重试3次
5、权限校验,白名单配置,只有白名单用户,可以执行函数
6、只需登录一次,后期调用无需登录
"""
import functools
import os
import time
import logging
import inspect
import traceback
from random import random
logging.basicConfig(filename='deco-2021-10-16.log',
level=logging.INFO,
format='[%(asctime)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
logger = logging.getLogger()
# 1、缓存,对数据进行缓存,如果文件大于290B,则清空文件,重新往里写入
def cache_txt(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
size = os.path.getsize('cache.txt')
logger.info('size:{}'.format(size))
if 0 < size < 290:
with open('cache.txt', 'r+', encoding='utf-8') as f:
logger.info(f.read())
f.write(res)
else:
with open('cache.txt', 'w', encoding='utf-8') as f:
f.write(res)
return res
return wrapper
@cache_txt
def add_value(param):
result = '缓存字符串信息,参数值为:' + param
return result
# 2、记录函数执行时间
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
func_args = inspect.getcallargs(func, *args, **kwargs)
start = time.time()
res = func(*args, **kwargs)
elapse = time.time() - start
logger.info('执行函数:{func_name},参数为:{param},执行时间为:{elapse}'.format(
func_name=func.__name__,
param=func_args,
elapse=round(elapse, 8)))
return res
return wrapper
@timer
def post_task(n):
count = 0
for i in range(n):
count += 1
return count
# 3、记录整个函数抛异常的日志
def log_except(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
trace_msg = traceback.format_exc()
msg = '函数{func_name}抛异常,异常信息为:{trace_msg}'.format(
func_name=func.__name__,
trace_msg=trace_msg
)
logger.error(msg)
return wrapper
@log_except
def division(value):
return 100/value
# 4、超时重试,如果设置超时,默认重试1次,也可以设置多次,如果没有设置超时,则不进行重试
def retry(timeout=None, retry_count=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
nonlocal retry_count
func_args = inspect.getcallargs(func, *args, **kwargs)
start = time.time()
res = func(*args, **kwargs)
elapse = time.time() - start
logger.info('耗时{elapse}秒'.format(elapse=elapse))
if timeout:
count = 1
while elapse > timeout:
start = time.time()
if retry_count > 0:
logger.info('进行第{count}重试,参数为:{func_args}'.format(
elapse=elapse,
timeout=timeout,
func_args=func_args,
count=count
))
res = func(*args, **kwargs)
elapse = time.time() - start
if elapse > 0:
logger.info('耗时{elapse}秒'.format(elapse=elapse))
count += 1
retry_count -= 1
return res
else:
return res
return wrapper
return decorator
# 超时重试,无日志版
def retry1(timeout=None, retry_count=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
nonlocal retry_count
start = time.time()
res = func(*args, **kwargs)
elapse = time.time() - start
if timeout:
while elapse > timeout:
start = time.time()
if retry_count > 0:
res = func(*args, **kwargs)
elapse = time.time() - start
retry_count -= 1
return res
else:
return res
return wrapper
return decorator
@retry(timeout=5, retry_count=3)
def query_sql(user_id):
t = 4.9 + random()
print(t)
time.sleep(t)
result = 'query result:' + user_id
return result
# 5、权限校验,白名单配置,只有白名单用户,运行执行函数
white_list = ['root', 'auth_user1', 'auth_user2']
def auth(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
func_args = inspect.getcallargs(func, *args, **kwargs)
# 假设被修饰函数里,包含param字典,且传入了username,这里假设很多接口参数都会username
username = func_args['param']['username']
if username in white_list:
return func(*args, **kwargs)
else:
msg = '用户{}没有权限'.format(username)
logger.error(msg)
raise Exception(msg)
return wrapper
@auth
def read_pay_sections(param):
print('业务逻辑。。。,参数为:', param)
return 0
req = {'username': 'abc', 'type': 1, 'phone': '138900000121'}
# read_pay_sections(req)
# 6、只需登录一次,后期调用无需登录
FLAG = False
def login(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
global FLAG
if FLAG:
return func(*args, **kwargs)
else:
username = input('please input your username: ')
password = input('please input your password: ')
if check(username, password):
FLAG = True
return func(*args, **kwargs)
return wrapper
def check(username, password):
# 可以写成查库来校验账号密码
sql = 'select password from user where username={}'.format(username)
# 这里写成测试用的
sql_password = '2021'
if sql_password == password:
return True
else:
return False
@login
def post_some_task(param):
print('----')
return param
post_some_task('abc')
post_some_task('dec')