Day 30 函数专题2 装饰器

一、知识点

装饰器是 Python 中实用的高阶函数特性,核心作用是:不修改原函数代码、不改变原函数调用方式的前提下,给函数增加额外功能。

1. 为什么需要装饰器

若多个函数需要添加同一类通用功能(如计时、日志),不用装饰器会导致重复代码(每个函数都要写一遍功能逻辑);装饰器可将通用功能抽离出来,一次定义、多处复用,原函数无需任何改动。

2. 装饰器的核心结构

装饰器本质是 "接收函数作为参数,返回新函数" 的高阶函数,核心分三层逻辑:

  1. 装饰器函数:接收被装饰的原函数作为参数;
  2. 嵌套的 wrapper(包装)函数:核心层,负责实现额外功能(如计时、日志),同时调用原函数,通过 * args/**kwargs 兼容原函数的任意参数,且保留原函数的返回值;
  3. 返回 wrapper 函数:用新的包装函数替换原函数,@装饰器名是简化写法(语法糖),等价于 "原函数 = 装饰器名 (原函数)"。

3. 核心执行流程(以计时装饰器为例)

调用装饰后的函数→实际执行的是 wrapper 函数→wrapper 先执行额外功能(如计时开始)→调用原函数执行核心逻辑→原函数执行完毕后,wrapper 再执行剩余额外功能(如计算并打印耗时)→最后返回原函数的返回值,保证原函数功能不受影响。

4. 关键细节

  1. *args/**kwargs:确保装饰器能适配有参数、无参数、多参数等任意形式的原函数,提升通用性;
  2. functools.wraps:可选优化,用于保留原函数的名称、注释等身份信息,避免装饰后函数 "身份" 变成 wrapper。

5. 常见应用场景

除了计时、日志,还可用于权限校验(如接口登录检查)、缓存(避免重复计算)、异常捕获(统一处理函数异常)、性能监控(统计调用次数 / 耗时)等。

核心总结

装饰器就像给函数 "穿外套":外套(wrapper)负责添加额外功能,里面的原函数本身不变,调用方式也和原来一样,却能多出来外套的功能。

二、普通函数与装饰器函数对比

1. 普通的函数

下面这个函数实现的是计算2到9999的所有质数(在大于 1 的自然数中,除了 1 和它自身外,不能被其他自然数整除的数),并且打印找到这些数需要的时间

    1. 定义一个判断是否为质数
    1. 定义一个函数,循环2到9999的数,通过判断质数函数来筛选每个数
    1. 在函数中通过time模块进行记时

会发现,这个time模块让整个代码逻辑很混乱,因为函数的主体是找质数,time模块是找质数的时间,如果可以time模块放在函数外,这样逻辑才清晰。

python 复制代码
import time


def is_prime(num):
    if num < 2:
        return False
    elif num == 2:
        return True
    else:
        for i in range(2, num):
            if num % i == 0:
                return False
        return True


def prime_nums():
    t1 = time.time()
    for i in range(2, 10000):
        if is_prime(i):
            print(i)
    t2 = time.time()
    print(f"执行时间:{t2 - t1}秒")

prime_nums()

2. 装饰器函数

python 复制代码
import time

# 定义一个装饰器
def display_time(func):
    def wrapper(): # 定义一个内部函数,在装饰器中wrapper函数是一个常用的函数名,并非强制,约定俗成的
        start_time = time.time()
        func()  # 直接调用原函数(无参数),这里的func()是指装饰器需要修饰的函数,在这里是prime_nums()
        end_time = time.time()
        print(f"执行时间: {end_time - start_time} 秒")
    return wrapper # return wrapper是返回函数对象,如果是return wrapper()则是立即执行wrapper函数

# 继续定义判断质数的函数
def is_prime(num):
    """
    判断一个数是否为素数
    """
    if num < 2:
        return False
    elif num == 2:
        return True
    else:
        for i in range(2, num):
            if num % i == 0:
                return False
        return True

# 装饰器的标准写法
@display_time
def prime_nums(): # 这2行是一个整体
    """
    找出2到10000之间的所有素数并打印
    """
    for i in range(2, 10000):
        if is_prime(i):
            print(i)


prime_nums()
# 执行时间每次都会变,但是变动不大,一般计算稳定的执行时间我们都是重复1000遍,然后取平均

装饰器的执行流程为:

  1. 定义装饰器函数 display_time:它接收一个函数 func 作为参数,并返回 wrapper 函数。
  2. 定义被装饰函数 prime_nums:此时 prime_nums 是一个普通函数对象。
  3. 应用装饰器:Python 自动将 prime_nums 作为参数传递给 display_time,即执行 display_time (prime_nums)。
  4. 替换原函数:display_time 返回 wrapper 函数,Python 用这个新函数覆盖了原来的 prime_nums。

也就是说装饰后,原函数名指向 wrapper,而非原始函数。

当你调用 prime_nums () 时,实际上执行的是 wrapper (),它会:

  1. 记录开始时间
  2. 调用 func ()(即原函数)
  3. 记录结束时间并打印耗时

这种等价的设计,会让初学者搞不懂为什么突然可以采取这种优雅的写法,类似的写法还有很多,在 python 中叫做语法糖:通过规范的写法来让代码更加优美和简洁,比如列表推导式也是,我们在后面再提。

你可以把 @理解为语法糖操作,实际上并非是 @装饰器,而是 @装饰器 + 下一行的代码 二者是一个整体。

3. 进一步拓展装饰器实现复用

可以看到,上述这个写法的时候,prime_nums()没有传入参数,如果函数有参数,那么必须给外部函数传入参数,也就是需要给外部的装饰器函数传入参数。

那么装饰器函数是需要复用的,不同的内部函数传入的参数不同,那就需要装饰器可以传入可变参数来维持这个特性。这就是说到了我们昨天的可变参数。

装饰器函数返回的是wrapper函数,所以,在调用装饰器函数的时候,返回的还是wrapper函数,而不是被修饰的函数。他是被修饰函数的外层函数,参数要大于等于被修饰函数的参数。

python 复制代码
import time

def display_time(func):
    """支持任意参数的时间统计装饰器"""
    def wrapper(*args, **kwargs):  # 接收任意数量的位置参数和关键字参数
        t1 = time.time()
        result = func(*args, **kwargs)  # 将参数传递给原函数,注意之前的无参数写法和现在不同
        t2 = time.time()
        print(f"函数执行时间: {t2 - t1} 秒")
        return result  # 返回原函数的返回值
    return wrapper

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

add(3, 5)  # 正常接收参数并计算

三、作业

python 复制代码
def logger(func):
    def wrapper(*args, **kwargs):
        print(f"开始执行函数{func.__name__}, 参数:{args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"函数 {func.__name__} 执行完毕. 返回值:{result}")
        return result
    return wrapper
@logger
def multiply(a, b):
    return a * b
multiply(2, 3)
html 复制代码
开始执行函数multiply, 参数:(2, 3), {}
函数 multiply 执行完毕. 返回值:6

勇闯python的第30天@浙大疏锦行

一些碎碎念念:

转眼间,跟着疏老师系统学习Python已整整30天。回首这段时光,收获满满,心中满是感激------由衷感谢您提供了如此优质的学习机会,让我得以深入探索这门语言的魅力,您辛苦了!

本科期间,我并非计算机专业,但曾接触过SPSS、R语言等相关知识,当时便对数据与编程的世界充满好奇。而这次系统学习Python,更让我感受到了不一样的惊喜:从未想过,一门编程语言竟能将数字的严谨与视觉的美感完美融合,绘制出如此多优美的图案。这种"用代码创造美"的体验,让我对编程的兴趣愈发浓厚,也彻底打破了我对"编程枯燥"的刻板印象。 经过这30天的系统沉淀,我不仅加深了对Python的理解,更找到了持续探索的动力。

计算机语言的世界奥秘无穷,我早已满怀期待,渴望在接下来的学习中解锁更多技能,进一步探索其中的精彩。未来,我也会带着这份热爱,在编程的道路上稳步前行,不负这段宝贵的学习时光。

相关推荐
张较瘦_1 小时前
[论文阅读] AI + 软件工程 | GenAI 赋能自适应系统:从技术突破到研究蓝图,一文看懂核心价值与挑战
论文阅读·人工智能·软件工程
START_GAME1 小时前
ComfyUI完全指南:从零正确配置GPU运算,彻底解决CPU运行与使用率低问题
人工智能
钛投标免费AI标书工具1 小时前
银奖·钛投标荣获华为技术有限公司主办昇腾AI大赛华中区决赛银奖
人工智能·深度学习·自然语言处理·知识图谱
nwsuaf_huasir1 小时前
深度学习1.3-软件篇-2025Pycharm添加导入anaconda中虚拟环境的python解释器以及相关Error解决方案
人工智能·python·深度学习
2301_800256111 小时前
8.3 查询优化 核心知识点总结
大数据·数据库·人工智能·sql·postgresql
互联网资讯2 小时前
融合AI大模型的Geo优化系统服务商如何选?避坑指南
大数据·人工智能·ai搜索优化·geo系统·geo优化系统·geo系统搭建
wan55cn@126.com2 小时前
人生如戏:换个片场,继续出演
人工智能·笔记·百度·微信
搞科研的小刘选手2 小时前
【广东财经大学主办】2026年人工智能与金融科技国际学术会议(IC-AIF 2026)
大数据·人工智能·金融·学术会议