纯净、安全、绿色的下载网站

首页

当前位置:首页IT学院IT技术

Python装饰器

Yabea   2020-01-29 我要评论

装饰器在Python中是一个强大的高级用法并且在流行Python框架中变得越来越常见经常会用到装饰器来增强函数的行为(动态的给一个对象添加一些额外的职责)包括记录日志权限校验性能测试数据封装等有了装饰器我们可以抽离出大量和函数功能本身无关的雷同代码并继续重用

Python装饰器有两种:

  1. 函数装饰器:管理函数调用和函数对象
  2. 类装饰器:管理类实例和类自身

为什么使用装饰器?

经常会遇到给函数或类增加新功能的场景当然我们可以使用函数调用或者其它技术来实现但是使用装饰器意图明确最小化扩展代码的冗余使用@语法糖相对优雅

装饰器的原理是什么?

我们先来看一个最简单的装饰器:

import time
from functools import wraps

def time_it(func):
    """
    输出函数的运行时间
    :param func:
    :return:
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func()
        end_time = time.time()
        process_time = end_time - start_time
        print(func.__name__, process_time)
        return result
    return wrapper

@time_it
def func_a():
    time.sleep(2)

对上述代码进行解释:

  1. time_it返回wrapper函数对象
  2. 使用time_it装饰func_a函数
  3. 调用被装饰的func_a函数会运行wrapper函数func_a其实是wrapper的引用

原理:我们知道Python中一切皆对象可以将函数作为其它函数的返回值可以看到装饰器的本质是一个函数返回一个函数对象通过"@"语法糖在包装函数中引入装饰器

装饰器的一个关键特性是在被装饰的函数定义之后立即执行

@wraps

上述装饰器中用到的了@wraps(func)在创建装饰器时一定要记得为包装函数添加functools库中的@wraps装饰器以保证函数的元数据(包括函数名函数注解等)不被丢失

当我们需要访问为被装饰器修饰的原包装函数时可以使用@wraps的__wrapped__属性来访问

内置装饰器

Python有三个内置装饰器:@staticmathod、@classmethod和@property

  • @staticmethod:类的静态方法跟成员方法的区别是没有self参数并且可以在类不进行实例化的情况下调用
  • @classmethod:跟成员方法的区别是接收的第一个参数不是self而是cls(当前类的具体类型)
  • @property:表示可以直接通过类实例直接访问的信息

装饰器嵌套

为了支持多步骤的扩展装饰器语法允许我们向一个装饰的函数或方法添加多个装饰器若多个装饰器同时装饰一个函数那么装饰器的调用顺序和@语法糖的声明顺序相反也就是:

@decorator1
@decorator2
def func():
    pass

等效于:

func = decorator1(decorator2(func()))

装饰器参数

函数装饰器和类装饰器都能接收参数这些参数传递给了真正返回装饰器的可调用对象而装饰器反过来又返回一个可调用对象

装饰器参数在装饰发生之前就解析了并且它们通常用来保持状态信息供随后的调用使用

上述实例中func_a()是没有参数的那如果添加参数的话装饰器该如何编写以接收参数呢?可以在装饰器中使用*args和**kwargs代替参数:

def time_it(func):
    """
    输出函数的运行时间
    :param func:
    :return:
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        process_time = end_time - start_time
        print(func.__name__, process_time)
        return result
    return wrapper

带参数的装饰器

我们有时候需要提供给被装饰的函数特定的功能需要在装饰器中带参数比如在业务处理中我们需要限定函数的执行超时时间由于每个函数所对应的超时时间不一样所以需要在装饰器中带参数以实现

装饰器的语法允许我们在调用时提供其它参数实现上述场景:

import time
import signal
import functools


def func_timeout(timeout):
    """
    超时时间装饰器
    :param timeout:
    :return:
    """
    def decorator(func):
        def handler(signum, frame):
            raise RuntimeError("run %s timeout !" % func.__name__)

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            signal.signal(signal.SIGALRM, handler)
            signal.alarm(timeout)
            func(*args, **kwargs)
            signal.alarm(0)
        return wrapper
    return decorator


@func_timeout(timeout=10)
def func():
    time.sleep(11)
    print("#" * 100)


if __name__ == '__main__':
    func()

类装饰器

上述实例都是函数装饰器相比函数装饰器类装饰器更加灵活主要依靠类的__call__方法当使用@形式将装饰器附加到函数上时就会调用此方法

举个例子:

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs):
        print("class decorator start")
        self._func(*args, **kwargs)
        print("class decorator end")


@Foo
def func():
    print("test123")

if __name__ == '__main__':
    func()

以上代码见 my github


相关文章

猜您喜欢

网友评论

Copyright 2022 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们