【单点知识】 Python装饰器介绍

文章目录

    • [0. 前言](#0. 前言)
    • [1. 为什么突然冒出"装饰器"?](#1. 为什么突然冒出“装饰器”?)
    • [2. 装饰器的基础:嵌套函数(函数里还能再"生"函数)](#2. 装饰器的基础:嵌套函数(函数里还能再“生”函数))
    • [3. 装饰器的常见用途](#3. 装饰器的常见用途)
    • [4. 常用装饰器代码实例](#4. 常用装饰器代码实例)
      • [4.1 最简骨架](#4.1 最简骨架)
      • [4.2 缓存装饰器(官方现成的)](#4.2 缓存装饰器(官方现成的))
      • [4.3 重试装饰器(带参数版)](#4.3 重试装饰器(带参数版))
    • [5. 小结](#5. 小结)

0. 前言

📣按照国际惯例,首先声明:本文只是我自己学习的理解,虽然参考了他人的宝贵见解及成果,但是内容可能存在不准确的地方。如果发现文中错误,希望批评指正,共同进步。

本文通过实例介绍Python中的装饰器。

1. 为什么突然冒出"装饰器"?

举个真场景: 产品经理说:"把这个Python脚本中的每个函数的运行时间打印出来。"

你最直观的写法:

python 复制代码
import time

def download():
    t0 = time.time()
    ...  # 真正的下载代码
    print(f"download 花了 {time.time()-t0:.2f} 秒")

def upload():
    t0 = time.time()
    ...  # 真正的上传代码
    print(f"upload 花了 {time.time()-t0:.2f} 秒")

问题立刻来了:

  1. 复制粘贴到吐,文件里全是 t0 = time.time()
  2. 哪天老板要改成写日志,你得全部再改一遍。
  3. 真正的业务(下载/上传)被淹没在一堆"边角料"里。
  4. 最最关键的是coding大佬根本不允许你动他的代码,说你写的都是屎山!

装饰器就是来解决这种"想给函数加点通用小功能,又不想改它本身"的痛点。

2. 装饰器的基础:嵌套函数(函数里还能再"生"函数)

Python 里函数像字符串、数字一样,可以当变量传来传去

嵌套函数 = 在函数 内部def 一个函数,通常用于:

  1. 隐藏实现细节
  2. 做闭包(记住外层变量的值)
  3. 给装饰器当"脚手架"

看个简单例子:

python 复制代码
def make_adder(base):      # 外层
    def adder(x):          # 内层
        return base + x    # 内层记住了 base
    return adder           # 把内层函数当"商品"返回

plus5 = make_adder(5)      # 现在 plus5 是一个"加 5"机器
print(plus5(7))            # 12

plus5 手里一直攥着 base=5,这就是闭包

装饰器全靠这个本事:外层把原函数"记住",内层在前后加点料,再返回内层函数即可。

3. 装饰器的常见用途

装饰器是一种语法糖,他的作用就是:"不碰原函数一行代码,就能给它前后塞点额外操作。" 它的常见用途有以下几类:

  • 打印日志 / 计时
  • 缓存结果(斐波那契算过一次就记住)
  • 重试网络请求
  • 检查用户权限
  • 路由注册(Flask、FastAPI)

语法糖的介绍请见:【单点知识】Python语法糖------让代码更甜的方法

4. 常用装饰器代码实例

4.1 最简骨架

python 复制代码
import time, functools

def timer(func):                # 外层:收到原函数
    @functools.wraps(func)      # 保留原函数名字&文档
    def wrapper(*args, **kw):   # 内层:真正会被调用的新函数
        t0 = time.time()
        result = func(*args, **kw)   # 调原函数
        print(f"{func.__name__} 花了 {time.time()-t0:.3f} 秒")
        return result
    return wrapper              # 把新函数返还给外界

使用:贴贴纸即可,例如

python 复制代码
import time, functools
import numpy as np

def timer(func):                # 外层:收到原函数
    @functools.wraps(func)      # 保留原函数名字&文档
    def wrapper(*args, **kw):   # 内层:真正会被调用的新函数
        t0 = time.time()
        result = func(*args, **kw)   # 调原函数
        print(f"{func.__name__} 花了 {time.time()-t0:.3f} 秒")
        return result
    return wrapper              # 把新函数返还给外界

@timer
def mm(x,y): return np.matmul(x,y)    

if __name__ =='__main__':
    x = np.random.rand(9999,9999)
    y = np.random.rand(9999,9999)
    mm(x, y)

输出为:

python 复制代码
mm 花了 4.049 秒

4.2 缓存装饰器(官方现成的)

@lru_cache 把函数每次的计算结果自动存进一张"备忘录",下次撞见相同输入直接拿答案,绝不再算第二次。 加一行就能让递归、重复查询等代码从"龟速"变"秒回"。

python 复制代码
from functools import lru_cache
import time

@lru_cache(maxsize=None)
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

t0 = time.time()
fib(999)          
print(f"耗时:{time.time() - t0}:.10" )

t0 = time.time()
fib(1000)        
print(f"耗时:{time.time() - t0}:.10")

输出为:

python 复制代码
耗时:0.0010073184967041016:.10
耗时:0.0:.10

第一次fib(999):把 fib(0)...fib(999) 共 1000 个值全部算出并缓存。

第二次fib(1000) :只需算 1 个新值fib(1000) = fib(999) + fib(998),fib(999) 和fib(998)这两个直接从缓存拿。

4.3 重试装饰器(带参数版)

重装饰器就是先收参数再返回真正的装饰器,所以一眼看过去就是 @xxx(arg) 的双层结构。

python 复制代码
def retry(times=3, wait=1):
    def decorator(func):
        @functools.wraps(func)  #这不是重装饰器
        def wrapper(*args, **kw):
            for i in range(1, times+1):
                try:
                    return func(*args, **kw)
                except Exception as e:
                    print(f"第{i}次失败:{e}")
                    if i == times:
                        raise
                    time.sleep(wait)
        return wrapper
    return decorator

@retry(times=5)
def connect():
    ...

@functools.wraps(func) 虽然"带括号",但它只是一次性返回真正装饰器的辅助工具,本身并不接受"用户自定义"参数,习惯上仍归为普通装饰器,而不是"带参装饰器(重装饰器)"。

5. 小结

  1. 嵌套函数 = 函数里 def 函数 + 闭包记忆
  2. 装饰器 = 嵌套函数 + 返回新函数 + @ 语法糖
  3. 口诀:"原函数不动,前后功能任意缝;撕掉装饰器,业务照样跑。"

记住:当你出现"又要给所有函数加同一件小事"的冲动时,先想装饰器------

写一次,贴万张,代码干净不慌张。

相关推荐
神仙别闹13 小时前
基于 C++ 实现(控制台)学生成绩管理系统
开发语言·c++
Y幽谷客13 小时前
Python加载本地大模型(Qwen3.5 8B)
python·大模型
伞伞悦读13 小时前
【第36期】Python 目录与路径详解:pathlib、文件遍历、创建、复制、移动和删除风险
开发语言·python
线上放牧人14 小时前
Windows删除图标缓存
windows·python·pyqt
Hrain-AI14 小时前
多 Agent 并行不打架:worktree 隔离与反馈回流落地(附脚本)
网络·数据库·人工智能·架构
qq_54702617914 小时前
Python 变量和简单数据类型
python
净水深流15 小时前
中央厨房冷链技术实践:多温区改造、WMS落地与IoT温控架构
大数据·数据库·人工智能·冷库冷链
l1t15 小时前
测试DuckDB 2.1的match_recognize模式匹配语句
数据库·duckdb
智搜广告15 小时前
智搜广告:科技行业AI回答优化公司如何破局
大数据·python·elasticsearch·geo
IpdataCloud16 小时前
AI智能体调用工具怎么核验来源IP?归属地、网络类型与代理风险识别(含Python代码)
数据库·python·tcp/ip