【单点知识】 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. 口诀:"原函数不动,前后功能任意缝;撕掉装饰器,业务照样跑。"

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

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

相关推荐
guodingdingh1 小时前
软件开发工作问题总结0718
java·开发语言·数据库
GuWenyue3 小时前
Cursor黑盒拆解!1套LangChain.js手写Mini编程Agent,自动生成React项目,效率提升60%
前端·数据库·人工智能
优橙教育3 小时前
5G网优培训 vs Java开发:转行选哪个?
java·开发语言·5g
想会飞的蒲公英4 小时前
计算机怎样读取中文文本:编码、清洗与标准化
人工智能·python·自然语言处理
Miao121315 小时前
某海外住宿平台如何在大规模场景下实现指标一致性:Minerva 指标平台实践
大数据·数据库·人工智能
SeaTunnel5 小时前
从 Python Script 地狱到标准化数据集成框架
大数据·开发语言·python·程序员·代码·seatunnel
CodexDave5 小时前
MySQL事务隔离级别与MVCC机制解析
前端·数据库·mysql·nginx·性能优化·负载均衡
深度研习笔记5 小时前
OpenCV工业视觉实战09|项目EXE打包+工控机无环境部署+后台常驻运行,彻底脱离Python环境,完成项目最终交付
python·opencv·webpack
碎光拾影5 小时前
ARM交叉工具链各工具作用及IMX6ULL平台LED+蜂鸣器裸机程序实现
java·开发语言·数据库
曾阿伦5 小时前
MongoDB 查询语句备忘手册
数据库·mongodb