5月16日day27打卡

发布于:2025-05-17 ⋅ 阅读:(16) ⋅ 点赞:(0)

函数专题2:装饰器

知识点回顾:

  1. 装饰器的思想:进一步复用
  2. 函数的装饰器写法
  3. 注意内部函数的返回值

作业:

编写一个装饰器 logger,在函数执行前后打印日志信息(如函数名、参数、返回值)

@logger
def multiply(a, b):
    return a * b

multiply(2, 3)  
# 输出:
# 开始执行函数 multiply,参数: (2, 3), {}
# 函数 multiply 执行完毕,返回值: 6

 作业代码:

def logger(func):
    def wrapper(*args, **kwargs):  # 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)  # 调用 multiply 函数,观察日志输出

 

  • func.__name__ 是「被装饰函数的名字」(比如这里 multiply 函数被装饰,func.__name__ 就是字符串 'multiply');
  • result 是「被装饰函数执行后的返回值」(比如 multiply(2,3) 会返回 6,所以 result 就是 6);
  • 整行代码会输出类似这样的信息:函数 multiply 执行完毕,返回值: 6

举个例子,当你调用 multiply(2, 3) 时,原函数会计算 2*3=6,这行代码就会在计算完成后「报个信」:「注意哦,multiply 函数跑完啦,它的结果是 6」。这样你就能清楚看到函数的执行结果是否符合预期。

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遍,然后取平均

 

 有个函数叫 prime_nums()它的任务是「计算一堆素数」,现在用 display_time 装饰它。就像给这个函数请了个「计时小助手」: 

当你调用 prime_nums() 时,实际会先触发小助手的工作流程:

  1. 小助手看一眼手表(记录开始时间);
  2. 小助手拍一下 prime_nums 的肩膀说:「该你干活了!」(这就是 func(),让原函数执行计算素数的操作);
  3. 等 prime_nums 干完活,小助手再看一眼手表(记录结束时间);
  4. 最后小助手告诉你:「刚才干活用了 X 秒」。

如果没有 func() 这行,小助手就只会看手表,但 prime_nums 根本不会去计算素数——原函数的功能就被装饰器「吞掉」了,这显然不是我们想要的。所以 func() 是在确保「原函数的活必须正常干」,装饰器只是在旁边记录时间而已。

 

 

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)  # 正常接收参数并计算

wrapper 可以理解为装饰器给原函数「定制的执行流程」,它的作用是把原函数的功能和装饰器要添加的额外功能(比如计时)整合在一起


假设你有一个函数 prime_nums()(功能是「计算素数」),现在用 display_time 装饰它(目标是「统计计算素数花了多久」)。这时候 wrapper 就像一个「中间人」:
当你调用 prime_nums() 时,实际执行的是 wrapper 函数的逻辑:

  1. wrapper 先做准备工作:记录开始时间(start_time = time.time());
  2. 然后让原函数 prime_nums 正常执行(func());
  3. 原函数执行完后,wrapper 做收尾工作:记录结束时间,计算耗时并打印(end_time = time.time() 和 print(...))。

简单说,wrapper 是装饰器用来「包裹」原函数的一层壳——它既保留了原函数的功能(通过 func() 调用),又在原功能的前后添加了新功能(这里是计时)。没有 wrapper,装饰器就无法在不修改原函数代码的情况下,给原函数「附加」新功能。

 

*args**kwargs这俩是装饰器里的「参数搬运工」,专门负责把你调用函数时传的参数「搬」给原函数用。

举个例子:
你定义了 add(a, b) 函数,调用时写 add(3, 5),这里的 3 和 5 是传给 add 的参数。但因为 add 被 display_time 装饰过,实际调用的是 wrapper 函数。这时候:

  • *args 会把 3, 5 这类「按顺序传的参数」(位置参数)打包成一个小箱子(元组)存起来(比如 args = (3, 5));
  • 如果调用时用了 add(a=3, b=5) 这种「带名字的参数」(关键字参数),**kwargs 会把它们打包成一个小字典存起来(比如 kwargs = {'a':3, 'b':5})。

然后 wrapper 里的 func(*args, **kwargs) 会把这两个箱子拆开,把参数原样传给原函数 add——这样 add 就能正常用 a=3, b=5 计算加法了。

简单说:*args 和 **kwargs 是为了让装饰器能适配「任何有参数的函数」——不管原函数要传几个参数、是哪种参数(位置/关键字),它们都能帮你「搬」过去,保证原函数正常工作

 @浙大疏锦行


网站公告

今日签到

点亮在社区的每一天
去签到