python——装饰器详解

劝君惜取少年时

装饰器

装饰器是修改其他函数的功能的函数(当然装饰器也可以装饰类)。它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,他们有助于让我们的代码更简短,也更Pythonic(Python范儿)

装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限验证等场景,装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

装饰器是一个函数,函数作为他的形参(装饰类的时候,类就是它的传参),返回值也是一个函数。

装饰器是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强), 避免功能增强时侵入传入函数

实际上,所谓函数装饰器,就是通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。

被装饰函数 = 装饰器函数(被装饰函数)

可以用@functionname的方式,简化调用

装饰器的格式

def logger(fn):  # 装饰器函数的传参是被装饰函数
    def _logger(*args, **kwargs):  # 装饰器内部嵌套函数的参数使用两个可变长参数,可以接受被装饰函数的任意数量和类型的参数
        print('before')
        ret = fn(*args, **kwargs)  # 调用传入的被装饰函数,以此符合装饰器的初衷:“在不修改原函数的前提下”
        print('after')
        return ret

    return _logger


@logger 
def add1(x, y):
    return x + y
# 等价于 add1 = logger(add1)

print(add1(4, 1000))

被装饰后的函数实质上已经是装饰器内部嵌套函数了,所以我们再打印这个函数的名字和文档说明,实际上是打印另外一个函数的名字和说明。如果仍想保有原函数的名称和文档说明,可以在装饰器内部嵌套函数内添加@wraps装饰器

from functools import wraps


def decorator_name(func):
    @wraps(func)  # 使用 functools.wraps 可以让打印出来的name还是原来的函数
    def decorated(*args, **kwargs):
        """这是装饰器内部嵌套函数"""
        print('before')
        func(*args, **kwargs)
        print('after')

    return decorated


@decorator_name  # 等价于:foo = decorator_name(foo)
def foo():
    """这是被装饰函数的文档说明"""
    return "Function is running"


print(foo.__name__)  # foo
print(foo.__doc__)   # 这是被装饰函数的文档说明
foo()

装饰器充当日志

from functools import wraps


def addition(func):
    @wraps(func)
    def with_log(*args, **kwargs):
        with open('out.log', mode='a') as log:
            log.write('>>>' + str(func.__name__) + '函数被调用\n')
        return func(*args, **kwargs)

    return with_log


@addition
def add(x, y):
    print(x + y)


add(4, 5)  # 9

python——装饰器详解_第1张图片

带参装饰器

在普通不带参的装饰器外层再套一层函数然后传入参数即可。

相当于给装饰器传入一个参数,把某些数值参数化,后再把这个装饰器返回,用这个传入过参数的装饰器 再去装饰 被装饰函数。

所以如果你想写带参装饰器,先写不带参的装饰器,再去包装一层函数, 对需要参数化的数值参数化即可。

继续以上面充当日志的装饰器为例,如果想把日志文件参数化,就可以使用带参装饰器

from functools import wraps


def decorator_have_args(log_file='out.log'):
    def addition(func):
        @wraps(func)
        def with_log(*args, **kwargs):
            with open(log_file, mode='a') as log:
                log.write('>>>' + str(func.__name__) + '函数被调用\n')
            return func(*args, **kwargs)

        return with_log

    return addition
"""
addition就是原来的装饰器,外面再套一层函数,加入想设置的参数,然后再把参数化的装饰器addition返回即可。
这样就可以指定日志输出的文件了
"""

@decorator_have_args('logg.log')  # 等价于 add = decorator_have_args('logg.log')(add)
def add(x, y):
    print(x + y)


add(4, 5)

装饰器的嵌套

Python 也支持多个装饰器,比如:

@funA
@funB
@funC
def fun():
    pass

上面程序的执行顺序是里到外,所以它等效于下面这行代码:

fun = funA(funB(funC(fun)))

装饰器的递归使用

实现一个可以重运行函数的带参装饰器,可以用来装饰任何一个功能函数,只要被装饰的函数执行抛出错误,则等待hung_time秒重新执行该函数,同一个函数最多重运行N次,hung_time和N由装饰器参数决定。

import time
from functools import wraps


def retry_args_decorator(repetition: int, hung_time: int):
    if type(repetition) != int or type(hung_time) != int:
        raise TypeError('装饰器参数类型错误!')

    def retry_decorator(func):
        NUM = {'number': 0}

        @wraps(func)
        def wrap(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                if NUM['number'] < repetition:
                    NUM['number'] += 1
                    print('发生错误!,原因:{} {}秒后,函数开始重试运行第{}次'.format(e, hung_time, NUM['number']))

                    # 设置等待时间
                    time.sleep(hung_time)

                    # 递归调用装饰器
                    wrap(*args, **kwargs)
                else:
                    raise Exception('函数已重复运行{}次,不可再运行!'.format(repetition))

        return wrap

    return retry_decorator

运行:

"""
函数test模拟3次抛出异常,第四次正常执行
"""
FLAG = 0


@retry_args_decorator(3, 1)
def test():
    global FLAG
    if FLAG < 3:
        FLAG += 1
        raise TypeError('类型错误!')
    else:
        print('success')


test()

结果:

发生错误!,原因:类型错误! 1秒后,函数开始重试运行第1次
发生错误!,原因:类型错误! 1秒后,函数开始重试运行第2次
发生错误!,原因:类型错误! 1秒后,函数开始重试运行第3次
success

用装饰器装饰类

装饰器实现单例模式
# 和函数装饰器相同,只不过传入的是类
def single_wrapper_1(cls):
    instance = None
		# 内嵌包装函数,也和函数装饰器相同
    def _instance(*args, **kwargs):
        
        nonlocal instance  # 由于instance是在上层局部作用域定义的,只能被访问,所以如果想修改它,必须使用nonlocal声明其为上级局部作用域中的instance
         # 如果instance不存在,也就是没有实例存在,调用cls进行实例化赋值给instance,否则就直接返回instance保存的实例,从而实现单例
        if not instance:
           	# 调用原来的类实例化
            instance = cls(*args, **kwargs) 
        return instance

    return _instance


@single_wrapper_1
class Test1:
    def __init__(self):
        self.num = 0

    def add(self):
        self.num = 99


ts1 = Test1()
ts2 = Test1()
print(ts1 is ts2)  # True
# 当然,如果你不想使用nonlocal,就可以创建字典来存放,因为字典是可变数据类型,修改字典内部的元素,并没有修改字典本身,所以在局部作用域修改字典内部元素是被允许的,并且在局部作用域可以访问字典 
def single_wrapper_2(cls):
    instance = {}

    def _instance(*args, **kwargs):
      	# 和上面方法判断逻辑一样,如果instance不存在,调用cls进行实例化,否则就直接返回instance
        if not instance.get(cls):
            instance[cls] = cls(*args, **kwargs)
        return instance

    return _instance


@single_wrapper_2
class Test2:
    def __init__(self):
        self.num = 0

    def add(self):
        self.num = 99


ts1 = Test2()
ts2 = Test2()
print(ts1 is ts2)  # True

你可能感兴趣的:(python专栏,python,开发语言)