python入门到入土---装饰器

装饰器(Decorator)是 Python 中最强大且常用的功能之一。它属于高阶函数 和 闭包的综合应用,广泛用于 Web 开发(如 Flask、Django)、日志记录、权限验证、性能监控等场景。

本文将从基本概念讲起,逐步介绍装饰器的实现机制与常见应用场景,并配合实际代码示例进行讲解。

一、什么是装饰器?

装饰器的本质就是一个函数,它接受一个函数作为参数,并返回一个新的函数。

它的作用是:在不修改原函数代码的前提下,增强或修改函数的行为。

二、装饰器的基本语法

1. 手动调用装饰器(不使用 @)

复制代码
def my_decorator(func):
    def wrapper():
        print("装饰器前处理")
        func()
        print("装饰器后处理")
    return wrapper

def say_hello():
    print("Hello, World!")

# 手动调用装饰器
new_say_hello = my_decorator(say_hello)
new_say_hello()

# 输出:
装饰器前处理
Hello, World!
装饰器后处理

2. 使用 `@` 语法糖(更简洁)

复制代码
def my_decorator(func):
    def wrapper():
        print("装饰器前处理")
        func()
        print("装饰器后处理")
    return wrapper

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

say_hello()

三、带参数的函数如何装饰?

1、如果原函数本身有参数怎么办?我们可以在 `wrapper` 中添加参数。

复制代码
def my_decorator(func):
    def wrapper(name):
        print("装饰器前处理")
        func(name)
        print("装饰器后处理")
    return wrapper

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

greet("Alice")

#输出 
装饰器前处理
Hello, Alice!
装饰器后处理

2、如果想让装饰器适用于任意参数,可以使用 `*args` 和 `**kwargs`:

复制代码
def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("装饰器前处理")
        func(*args, **kwargs)
        print("装饰器后处理")
    return wrapper

@my_decorator
def add(a, b):
    print(f"{a} + {b} = {a + b}")

add(3, 5)

3、拓展 *args和**kwargs介绍

*args用于传递不定数量的位置参数,它会将传递给函数的位置参数收集到一个元组中。例如

复制代码
def my_func(*args):
    for arg in args:
        print(arg)

# 调用函数时,可以传入任意数量的入参
my_func(1, 2, 3)

**kwargs用于传递不定数量的关键字参数,它会将传递给函数的关键字参数收集到一个字典中。例如:

复制代码
def my_func(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

my_func(a=1, b=2, c=3)

在装饰器中使用 *args 和 **kwargs 是一个非常常见的做法,这样可以确保装饰器对各种函数都通用。

四、带参数的装饰器(装饰器的装饰器)

有时我们希望装饰器本身也能接受参数。这就需要嵌套一层函数。

复制代码
def repeat(num_times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def hello(name):
    print(f"Hello, {name}!")

hello("John")

# 输出
Hello, John!
Hello, John!
Hello, John!

# 解析
这是一个带参数的装饰器,它的结构可以分为三层:

最外层函数 repeat(num_times):
接收一个参数 num_times,表示重复执行多少次。
返回内部的装饰器函数 decorator(func)。

中间层 decorator(func):
接收一个函数 func,也就是要装饰的目标函数(例如 hello)。
返回包装函数 wrapper(*args, **kwargs)。

最内层 wrapper(*args, **kwargs):
接收任意的位置参数和关键字参数。
在其中循环调用目标函数 func,次数由 num_times 决定。
最终返回最后一次调用的结果。

五、使用 `functools.wraps` 保留元信息

1、 元信息

在 Python 中,每一个函数对象都有自己的元信息属性,例如:

  • func.name: 函数的名字
  • func.doc: 函数的文档注释(docstring)
  • func.module: 函数所属模块
  • func.annotations: 参数和返回值的注解

2、装饰器会遮盖原函数的 元数据信息

复制代码
def log(func):
    def wrapper(*args, **kwargs):
        print(f"[LOG] 函数 {func.__name__} 正在执行")
        result = func(*args, **kwargs)
        print(f"[LOG] 函数 {func.__name__} 执行完成")
        return result
    return wrapper

@log
def say_hi():
    """打招呼函数"""
    print("Hi!")

print(say_hi.__name__) # wrapper 
print(say_hi.__doc__) #None

--- 装饰器返回的是wrapper,@log 应用到 say_hi 时,其实 say_hi 变成了 wrapper 的引用,而不是原来的 say_hi

3、如何保留元信息

Python 提供了一个标准库工具 functools.wraps,它能自动将装饰器中的 wrapper 函数"伪装"成原函数,从而保留其元信息。

复制代码
from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[LOG] 函数 {func.__name__} 正在执行")
        result = func(*args, **kwargs)
        print(f"[LOG] 函数 {func.__name__} 执行完成")
        return result
    return wrapper

@log
def say_hi():
    """打招呼函数"""
    print("Hi!")

print(say_hi.__name__)  # 输出: say_hi
print(say_hi.__doc__)   # 输出: 打招呼函数

六、装饰器的实际应用场景

1. 日志记录

复制代码
def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[LOG] 函数 {func.__name__} 正在执行")
        result = func(*args, **kwargs)
        print(f"[LOG] 函数 {func.__name__} 执行完成")
        return result
    return wrapper

@log
def divide(a, b):
    return a / b

2. 访问权限验证

复制代码
from functools import wraps

# 模拟当前登录用户的角色
current_user_role = 'admin'  # 可以设置为 'user' 测试无权限情况


def permission_required(role):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if current_user_role == role:
                print(f"[权限验证通过] 欢迎, {role.upper()}!")
                return func(*args, **kwargs)
            else:
                raise PermissionError(f"[权限不足] 当前用户无权访问此功能,所需角色: {role}")
        return wrapper
    return decorator

# 使用示例

@permission_required('admin')
def delete_database():
    """管理员专用功能: 删除数据库"""
    print("正在删除数据库...")

@permission_required('user')
def read_data():
    """普通用户功能: 查看数据"""
    print("正在查看数据...")

# 测试 admin 用户
try:
    delete_database()
except PermissionError as e:
    print(e)

#输出
[权限验证通过] 欢迎, ADMIN!
正在删除数据库...

# 测试 user 用户
current_user_role = 'user'
try:
    delete_database()
except PermissionError as e:
    print(e)

#输出
[权限不足] 当前用户无权访问此功能,所需角色: admin
[权限验证通过] 欢迎, USER!

3. 性能监控

复制代码
import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 执行耗时:{end - start:.6f} 秒")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)

slow_function()

七、多层装饰器叠加(多个装饰器)

装饰器可以像洋葱一样层层包裹,写法如下:

而装饰器的执行顺序,是从下往上执行的

复制代码
@decorator1
@decorator2
def hello():
    pass

等价于

def hello():
    pass

hello = decorator1(decorator2(hello))

八、总结:为什么使用装饰器?

优点 说明
代码解耦 不修改原函数即可添加额外功能
高复用性 同一个装饰器可用于多个函数
可组合性强 多个装饰器可以组合使用
支持参数 装饰器本身也可以接收参数
增强可读性和可维护性 提升代码结构清晰度

拓展

装饰器是 Python 面向切面编程(AOP)思想的重要体现,是构建高质量程序的关键工具之一

特性 Python 装饰器 Java AOP
实现方式 原生语法(@decorator 依赖框架(如 AspectJ、Spring AOP)
应用范围 单个函数或类 类、包、甚至整个应用
执行时机 运行时 编译期、类加载期或运行时(取决于实现)
复杂度 简单灵活 较复杂,需要学习框架
适用场景 轻量级、函数式编程 企业级、模块化、大型项目
是否侵入原代码 不侵入(通过包装) 通常不侵入(通过代理模式)
  • Python 装饰器就像是你穿了一件外套,别人看不出你原本的样子,但你知道里面是你。
  • Java AOP 更像是你被替换成了一架机器人的外观,但内部还是你,只是动作都被替换了。
相关推荐
程序新视界5 小时前
在连表查询场景下,MySQL隐式转换存在的坑
数据库·mysql·dba
九河云5 小时前
在云计算环境中实施有效的数据安全策略
大数据·网络·数据库·云计算
爱吃烤鸡翅的酸菜鱼6 小时前
从数据库直连到缓存预热:城市列表查询的性能优化全流程
java·数据库·后端·spring·个人开发
dualven_in_csdn6 小时前
ubuntu离线安装 xl2tpd
linux·数据库·ubuntu
初听于你8 小时前
高频面试题解析:算法到数据库全攻略
数据库·算法
BTU_YC14 小时前
Neo4j查询计划完全指南:读懂数据库的“执行蓝图“
数据库·neo4j
非极限码农14 小时前
Neo4j图数据库上手指南
大数据·数据库·数据分析·neo4j
mit6.82415 小时前
[C# starter-kit] 命令/查询职责分离CQRS | MediatR |
java·数据库·c#
苏打水com15 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端