一、概念及作用

     Python 装饰器(Decorator)允许在不修改原有函数或类代码的情况下,为其动态地添加新功能。类比于生活中的“包装袋”,在不改变内部物品(原函数)的前提下,增加新的特性,如印上Logo(日志记录)等。

     应用到项目中,设想一个场景,为项目中的多个函数都加上计时功能,以监控它们的性能,该如何设计?如果不使用装饰器,可能需要在每个函数的开头和结尾都写上记录时间的代码,这会导致大量重复代码。装饰器就是为了解决这类“横切面”而生的,它将通用逻辑(如日志、计时、权限校验)封装起来,然后通过 @ 语法糖应用到需要的地方,让代码更简洁、优雅且易于复用。

二、pyhton装饰器的核心原理

  基于以下三个 Python 的核心概念:

  • 函数即对象:在 Python 中,函数可以被赋值给变量、作为参数传递给其他函数,也可以作为另一个函数的返回值。

  • 高阶函数:装饰器本质上就是一个高阶函数,它接收一个函数作为参数,并返回一个新的函数。

  • 闭包:装饰器通常利用闭包来“记住”被装饰的函数,并在其内部定义的包装函数中使用它。

    也就是说,装饰器在函数定义时就被执行了,它用返回的新函数替换了原来的函数。

原理拆解:

# 函数也是一个对象,也可以作为参数。且函数对象可以作为返回值,赋值给变量。
def decor_a(func):
    def decor_b():
        print("decor b")
        func()
        print("after decor b")

    return decor_b

def decor_c():
    print("decor_c")

decor_c = decor_a(decor_c)

decor_c()

输出结果:
decor b
decor_c
after decor b

三、用法

1、无参数装饰器(基础装饰器)

def my_decorator(func):
    def wrapper():
        print("在函数执行前做点什么")
        func() # 调用原函数
        print("在函数执行后做点什么")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
# 输出:
# 在函数执行前做点什么
# Hello!
# 在函数执行后做点什么

2、支持任意参数的装饰器

      为了让装饰器能应用于任何函数,包装函数需要使用*args和**kwargs来接收并传递所有参数。

def logger(func):
    def wrapper(*args, **kwargs):
        print(f"正在调用函数: {func.__name__}")
        result = func(*args, **kwargs)
        # 记得要返回原函数的结果
        return result 
    return wrapper

@logger
def add(a, b):
    return a + b

print(add(2, 6))
# 输出:
# 正在调用函数: add
# 8

3、保留原函数元信息 (@functools.wraps)

      使用装饰器后,原函数的__name__、__doc__等属性会变成包装函数的属性。为了保留这些信息,必须使用functools.wraps。这是一个非常重要的好习惯。

import functools

def logger(func):
    # 修复元信息
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"正在调用: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@logger
def my_func():
    """测试字符串"""
    pass

print(my_func.__name__) # 输出: my_func (而不是 wrapper)
print(my_func.__doc__)  # 输出: 测试字符串

4、类装饰器

       如果装饰器需要维护状态(如统计函数被调用的次数),使用类来实现会更清晰。关键是实现__call__方法,使类的实例可以像函数一样被调用。

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} 已被调用 {self.count} 次")
        return self.func(*args, **kwargs)

@CountCalls
def greet(name):
    print(f"Hello, {name}")

greet("Tom")
# 输出: greet 已被调用 1 次; Hello, Tom
greet("Bob")
# 输出: greet 已被调用 2 次; Hello, Bob

当一个函数被多个装饰器修饰时,它们的执行顺序是就近原则,即从下往上(由内向外)。

@a
@b
@c
def my_function():
    pass
  • 装饰阶段:先执行 c,再执行 b,最后执行 a。

  • 调用阶段:调用 my_function() 时,会先进入 a 的逻辑,然后是 b,再到 c,最后才执行 my_function 本身。

更多推荐