当前位置: 首页 > news >正文

Python 深入浅出装饰器

本篇文章将详细介绍装饰器的来龙去脉!

参考文章:Python 装饰器 | 简单一点学习 easyeasy.me

1. 装饰器是啥?

装饰器(Decorator)本质上是一个函数(或类),它接受一个函数作为输入,然后返回一个新的函数。新的函数通常会在原函数的基础上增加一些功能,比如记录日志、计时、权限验证等。

用生活化的比喻,装饰器就像是给你的手机壳加个防摔功能。手机(原函数)还是那个手机,但壳(装饰器)让它多了点保护功能。你不用改手机本身,就能让它更耐用。

在 Python 中,装饰器通常用 @ 符号来写,比如:

@my_decorator
def my_function():print("我是一个函数!")

这里的 @my_decorator 就是在 my_function 上加了个装饰器。接下来我们一步步揭开它的神秘面纱。


2. 函数的基础知识

在讲装饰器之前,得先搞清楚 Python 函数的一些基本概念,因为装饰器本质上是对函数的操作。

2.1 函数是“第一等公民”

在 Python 中,函数可以:

  • 赋值给变量
  • 作为参数传递给其他函数
  • 作为返回值从函数返回

来看个例子:

def say_hello():print("Hello!")# 赋值给变量
greet = say_hello
托管
greet()  # 输出:Hello!# 作为参数传递
def call_func(func):func()call_func(say_hello)  # 输出:Hello!

这说明函数可以像普通变量一样被操作,为装饰器提供了基础。

2.2 闭包和内部函数

装饰器经常用到闭包(closure),也就是函数里定义的函数能记住外部函数的变量。简单例子:

def outer():x = 10def inner():print(x)  # 能访问外层的 xreturn innerfunc = outer()
func()  # 输出:10

这个特性让装饰器可以“包装”函数并保留额外的信息。


3. 从简单例子入手

让我们直接写一个简单的装饰器,感受一下它的威力。

3.1 例子:记录函数执行时间

假设我们想知道一个函数跑了多久,可以用装饰器来实现:

import timedef timer(func):def wrapper():start = time.time()func()end = time.time()print(f"{func.__name__} 运行时间:{end - start} 秒")return wrapper@timer
def slow_function():time.sleep(2)print("我睡了 2 秒")slow_function()
# 输出:
# 我睡了 2 秒
# slow_function 运行时间:2.002345 秒

3.2 发生了什么?

  • @timer 等价于 slow_function = timer(slow_function)
  • timer 函数接收 slow_function 作为参数,返回一个新的函数 wrapper
  • wrapper 函数在调用 func()(即 slow_function)前后加了计时代码。

这个例子展示了装饰器的基本逻辑:用一个函数包装另一个函数,增加新功能。


4. 装饰器的工作原理

为了更深入理解,我们来拆解一下装饰器的结构。

4.1 装饰器的本质

装饰器是一个高阶函数,接收一个函数,返回一个新的函数。新函数通常会调用原函数,并在前后加点“料”。

上面的 timer 装饰器可以改写成这样(去掉 @ 语法糖):

def slow_function():time.sleep(2)print("我睡了 2 秒")slow_function = timer(slow_function)
slow_function()

4.2 wrapper 函数的参数问题

上面的例子有个问题:如果 slow_function 有参数怎么办?我们需要让 wrapper 函数能接受任意参数。

改进版:

def timer(func):def wrapper(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"{func.__name__} 运行时间:{end - start} 秒")return resultreturn wrapper@timer
def greet(name):time.sleep(1)print(f"你好,{name}!")return f"问候 {name}"result = greet("小明")
# 输出:
# 你好,小明!
# greet 运行时间:1.00123 秒
# result = "问候 小明"

这里用了 *args**kwargs 来处理任意参数和关键字参数,还返回了原函数的返回值。


5. 带参数的装饰器

有时候,我们希望装饰器本身也能接受参数。比如,控制日志的级别:

def log(level):def decorator(func):def wrapper(*args, **kwargs):print(f"[{level}] {func.__name__} 被调用")return func(*args, **kwargs)return wrapperreturn decorator@log("INFO")
def say_hello(name):print(f"你好,{name}!")say_hello("小明")
# 输出:
# [INFO] say_hello 被调用
# 你好,小明!

5.1 原理拆解

带参数的装饰器多了一层函数嵌套:

  • log(level) 返回一个装饰器函数 decorator
  • decorator 再接收目标函数 func,返回 wrapper
  • @log("INFO") 等价于 say_hello = log("INFO")(say_hello)

这让装饰器变得更灵活,可以根据参数定制行为。


6. 装饰器的实际应用场景

装饰器在实际开发中超级实用,以下是一些常见的场景。

6.1 日志记录

像上面的 log 装饰器,可以记录函数的调用信息:

def log(func):def wrapper(*args, **kwargs):print(f"调用 {func.__name__},参数:{args}, {kwargs}")return func(*args, **kwargs)return wrapper

6.2 权限验证

比如,只有管理员才能调用某些函数:

def require_admin(func):def wrapper(user, *args, **kwargs):if user.get("role") == "admin":return func(user, *args, **kwargs)else:raise PermissionError("需要管理员权限")return wrapper@require_admin
def delete_user(user, user_id):print(f"用户 {user_id} 已被删除")user = {"role": "admin"}
delete_user(user, 123)  # 正常执行
user = {"role": "guest"}
# delete_user(user, 123)  # 抛出 PermissionError

6.3 缓存

可以用装饰器实现函数结果缓存,提高性能:

def cache(func):cache_dict = {}def wrapper(*args):if args in cache_dict:return cache_dict[args]result = func(*args)cache_dict[args] = resultreturn resultreturn wrapper@cache
def fib(n):if n <= 1:return nreturn fib(n-1) + fib(n-2)print(fib(10))  # 计算很快,因为中间结果被缓存

7. 高级用法:类装饰器和多重装饰器

7.1 类装饰器

装饰器不一定是函数,也可以用类来实现:

class Timer:def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):start = time.time()result = self.func(*args, **kwargs)end = time.time()print(f"{self.func.__name__} 运行时间:{end - start} 秒")return result@Timer
def slow_function():time.sleep(2)print("我睡了 2 秒")slow_function()

类装饰器通过 __call__ 方法让对象可以像函数一样被调用,适合需要保存状态的场景。

7.2 多重装饰器

可以给一个函数加多个装饰器:

@log("INFO")
@timer
def greet(name):time.sleep(1)print(f"你好,{name}!")greet("小明")
# 输出:
# [INFO] greet 被调用
# 你好,小明!
# greet 运行时间:1.00123 秒

多重装饰器的执行顺序是从内到外(由下到上),但定义顺序相反。比如,log 先执行,然后是 timer


8. 常见坑和怎么避坑

8.1 函数元信息丢失

装饰器可能会覆盖原函数的元信息(比如 __name____doc__)。可以用 functools.wraps 解决:

from functools import wrapsdef timer(func):@wraps(func)def wrapper(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"{func.__name__} 运行时间:{end - start} 秒")return resultreturn wrapper@timer
def greet(name):"""问候用户"""print(f"你好,{name}!")print(greet.__name__)  # 输出:greet
print(greet.__doc__)   # 输出:问候用户

@wraps(func) 会保留原函数的元信息。

8.2 性能问题

装饰器会增加函数调用的开销,尤其是在嵌套多层装饰器时。尽量保持 wrapper 函数简单,避免复杂逻辑。

8.3 调试困难

装饰器可能会让代码逻辑变得复杂,调试时可以用 print 或日志记录 wrapper 的执行过程。


9. 总结与实践建议

装饰器是 Python 中一个强大且灵活的工具,能让你的代码更简洁、更模块化。

  • 装饰器的本质是函数(或类)包装,增加功能。
  • 简单装饰器用 @ 语法糖,带参数的装饰器多一层嵌套。
  • 实际应用场景包括日志、权限、缓存等。
  • 注意元信息丢失问题,用 functools.wraps 解决。
http://www.dtcms.com/a/320416.html

相关文章:

  • 2026计算机毕业设计选题推荐:如何通过项目实用性来选择创新且高通过率的课题
  • Dify-16: 开发环境配置
  • 【MySQL】SQL优化
  • Linux Shell为文件添加BOM并自动转换为unix格式
  • C++之队列浅析
  • 每日算法刷题Day58:8.7:leetcode 单调栈5道题,用时2h
  • 零基础-动手学深度学习-9.3. 深度循环神经网络
  • Langchain入门:对话式RAG
  • Tool Learning的基本概念及应用
  • 数据结构——栈、队列
  • python题目练习 无重叠区间
  • Linux学习-数据结构(二叉树)
  • 嵌入式开发学习———Linux环境下IO进程线程学习(六)
  • 了解大型语言模型:力量与潜力
  • SpringBoot学习日记 Day5:解锁企业级开发核心技能
  • PCIe Base Specification解析(九)
  • 多线程的使用
  • 2025 最新 ECharts 下载、安装与配置教程
  • Linux 中断系统全览解析:从硬件到软件的全路线理解
  • Oracle 19C In-Memory 列存储技术测试
  • Qwen系列模型
  • [链表]两两交换链表中的节点
  • 【感知机】感知机(perceptron)学习算法的对偶形式
  • aurora rx没有ready信号
  • 哈希表——指针数组与单向链表的结合
  • linux顽固进程查看并清理
  • Java包装类详解与应用指南
  • SupChains技术团队:需求预测中减少使用分层次预测(五)
  • 目标检测数据集 - 眼睛瞳孔检测数据集下载「包含COCO、YOLO两种格式」
  • 菜鸟笔记007 [...c(e), ...d(i)]数组的新用法