【Python】深入解析装饰器(Decorator)的使用指南

文章目录

  • 前言
  • 一、装饰器是什么?
  • 二、装饰器执行场景
  • 1. 嵌套函数
  • 2. 装饰器语法
  • 3. 带参数的函数调用装饰器
  • 4. 带参数的装饰器
  • 5. 类作为装饰器
  • 6. 内置装饰器
  • 7. 多个装饰器
  • 总结

  • 前言

    装饰器(Decorator)是Python中一种强大的语法特性,它允许你在不修改原函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接收一个函数作为参数,并返回一个新的函数。


    一、装饰器是什么?

    装饰器是一个函数,在python中它有着特殊的写法,通过@func写在执行函数前面,此时这个执行函数就会通过参数去执行这个装饰器。所以更加简单的理解就是整个只是个函数嵌套的过程。

    二、装饰器执行场景

    装饰器的执行场景:

  • 日志记录: 记录函数的调用信息
  • 性能测试: 记录函数的执行时间
  • 权限校验: 校验函数的登录权限等
  • 函数校验: 校验函数执行的合理性
  • 1. 嵌套函数

    先把装饰器拆开成普通的函数嵌套,如下

    def my_decorator(func):
        print("before excuse fuc...")
        func()
        print("end excuse fuc...")
    
    
    def say_hello():
        print("hello")
    
    
    my_decorator(say_hello)
    

    执行结果:

    这里装饰器函数接收一个参数参数,然后在内部嵌套执行

    注意: 函数A调用装饰器函数B,是指装饰器函数B接收函数A为参数,然后调用执行装饰器函数B,防止颠倒这个执行对象。

    2. 装饰器语法

    假如不对装饰器函数修改,直接调用装饰器:

    def my_decorator(func):
        print("before excuse fuc...")
        func()
        print("end excuse fuc...")
    
    
    @my_decorator
    def say_hello():
        print("hello")
    
    
    say_hello()
    

    执行结果如下:

    显然,装饰器的执行过程不是简单的套用嵌套,而是函数替换, 实际的装饰器函数应该如下:

    say_hello = my_decorator(say_hello)
    

    如果像上面,装饰器函数my_decorator返回值是Nonesay_hello函数替换后就是None,造成后续执行失败。所以通常的装饰器写法,需要中间嵌套 wrapper函数,然后将其返回。

    def my_decorator(func):
        def wrapper():
            print("before excuse fuc...")
            func()
            print("end excuse fuc...")
        return wrapper
    
    
    @my_decorator
    def say_hello():
        print("hello")
    
    
    say_hello()
    

    执行结果如下:


    注意:函数wrapper()和wrapper, 是两个不同的概念,say_hello是指函数对象(函数指针),它不会被立即执行,所以返回函数的时候应该返回的是wrapper,在外面被调用执行。

    3. 带参数的函数调用装饰器

    假如函数带参数:

    def decorator_with_args(func):
        def wrapper(*args, **kwargs):
            print(f"函数 {func.__name__} 被调用,参数: {args}, {kwargs}")
            result = func(*args, **kwargs)
            print(f"函数 {func.__name__} 执行完毕")
            return result
    
        return wrapper
    
    
    @decorator_with_args
    def add(a, b):
        return a + b
    
    
    print(add(3, 5))
    

    参数a, b 传给add函数,在装饰器里面wrapper相当于add方法,*args 捕获所有位置参数,得到 args = (3, 5),**kwargs 捕获所有关键字参数(本例中没有,故为空),执行结果返回add的计算结果。

    4. 带参数的装饰器

    假如装饰器带参数:在原有的装饰器上再嵌套一层用来传递参数。

    
    def repeat(num_times):
        def decorator_repeat(func):
            def wrapper(*args, **kwargs):
                for _ in range(num_times):
                    result = func(*args, **kwargs)
                return result
            return wrapper
        return decorator_repeat
    
    @repeat(num_times=3)
    def greet(name):
        print(f"Hello {name}")
    
    greet("Alice")
    

    5. 类作为装饰器

    类作为装饰器,执行__call__函数:say_hello函数作为参数传递给类CountCalls,此时say_hello是类CountCalls的实例化对象,开始执行init函数,初始化_init_函数(self.func = say_hello),完成函数替换执行__call__返回的函数。

    class CountCalls:
        def __init__(self, func):
            self.func = func
            self.num_calls = 0
    
        def __call__(self, *args, **kwargs):
            self.num_calls += 1
            print(f"调用次数: {self.num_calls}")
            return self.func(*args, **kwargs)
    
    @CountCalls
    def say_hello():
        print("Hello!")
    
    say_hello()
    say_hello()
    

    6. 内置装饰器

    @classmethod是类方法,他可以直接别类调用,同时可以访问和修改类属性。对于@staticmethod静态方法,他不需要实例化,可以直接被类名调用,同时不可以访问类属性,或实习化对象。下面代码的name属性,通过@name.setter来赋值(复制同时做校验),和@property装饰器把方法变成属性被对象访问。

    class MyClass:
        def __init__(self):
            self._name = None
    
        @classmethod
        def class_method(cls):
            print("这是一个类方法")
    
        @staticmethod
        def static_method():
            print("这是一个静态方法")
    
        @property
        def name(self):
            return self._name
    
        @name.setter
        def name(self, value):
            if not isinstance(value, str):
                raise ValueError("name should be str")
            self._name = value.title()
    
    
    if __name__ == '__main__':
        MyClass.class_method()
        MyClass.static_method()
        obj = MyClass()
        obj.name = "andy"
        print(obj.name)
    
    

    7. 多个装饰器

    对于函数可以调用多个装饰器,多个装饰器都可以起到校验作用,校验顺序如下,且在装饰器的函数同步执行。

    def decorator1(func):
        def wrapper():
            print("装饰器1 - 前")
            func()
            print("装饰器1 - 后")
    
        return wrapper
    
    
    def decorator2(func):
        def wrapper():
            print("装饰器2 - 前")
            func()
            print("装饰器2 - 后")
    
        return wrapper
    
    
    @decorator1
    @decorator2
    def my_function():
        print("原函数")
    
    
    my_function()
    
    

    输出结果为:


    总结

    以上就是装饰器常用场景,装饰器的使用多数为校验或记录执行记录,常常用在登录鉴权,或数据校验场景。

    作者:大执

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Python】深入解析装饰器(Decorator)的使用指南

    发表回复