python装饰器(decorator)

初学者如何理解装饰器

刚开始学习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')

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值