Python装饰器知多少

目标

        熟悉装饰器的作用和基本使用方法,熟悉Python的一些常见的装饰器方法。


Python版本

        Python 3.9.18


官方文档

装饰器https://docs.python.org/3.9/glossary.html#term-decorator


概述

装饰器

官方文档

A function returning another function, usually applied as a function transformation using the @wrapper syntax. Common examples for decorators are classmethod() and staticmethod().

decorators ::= decorator+
decorator ::= "@" assignment_expression NEWLINE

理解

  1. 装饰器本质上是一个接受函数作为参数并返回一个新函数的函数,这个新函数通常在原函数执行前后添加额外的行为。
  2. decorators ::= decorator+表示一个函数或方法可以有多个装饰器。
  3. decorator ::= "@" assignment_expression NEWLINE描述了单个装饰器的结构,它由一个@符号引入,后面跟着一个赋值表达式(装饰器函数),然后是换行。

语法糖

        语法糖本质是通过更加简洁、易读的语法来表达原本可以用更复杂或冗长的方式实现的功能。比如JDK8的新特性就有Lambda表达式,它简化了复杂的代码,可以理解为语法糖。


自定义装饰器

没有参数传递

需求

        做一个性能监控的装饰器,用于计算方法或函数的耗时。

import time

def calculation_time(fun):
    def wrapper(*args, **kwargs):
        print(f"传如方法的id:{id(fun)}")
        print(f"wrapper方法的id:{id(wrapper)}")
        #开始时间
        start_time = time.time()
        result = fun(*args, **kwargs)
        end_time = time.time()
        print(f"方法或函数名称:{fun.__name__};运行耗时:{end_time-start_time}秒。")
        print(f"这是方法或函数的返回值:{result},如果被修饰的方法或函数没有返回值,则可以省略,但为了代码的健壮性我们往往会把返回值加上。")
        return result
    return wrapper

@calculation_time
def fun():
    print(f"fun方法的id:{id(fun)}")
    time.sleep(2)
    print("hello world!")

if __name__ == '__main__':
    fun()

输出结果

传如方法的id:2059984063360
wrapper方法的id:2059984066560
fun方法的id:2059984066560
hello world!
方法或函数名称:fun;运行耗时:2.0003597736358643秒。
这是方法或函数的返回值:None,如果被修饰的方法或函数没有返回值,则可以省略,但为了代码的健壮性我们往往会把返回值加上。

总结

代码中定义的装饰器有两个函数:

  • 第一个函数:返回一个包装过的函数wrapper,实际上就是替换了fun方法,这个结论从输出的id值来看得到了验证。
  • 第二个函数:对原函数或方法的增强。如果原函数或方法没有返回值,我们可以省略第二个函数的返回值。但是为了代码的健壮性,我们往往会加上返回值。
  • 通过注解调用装饰器是语法糖,我们还可以手动调用装饰器,这在语义上是等价的。
if __name__ == '__main__':
    fun = calculation_time(fun)
    fun()

有参数传递和返回值

需求

        创建一个函数,用于处理学生基本信息。创建装饰器,在函数被调用前做参数验证。

def validate_student_info(func):
    def wrapper(*args, **kwargs):
        # 参数验证
        name, age, sex, scores, hobbies = args

        # 验证 name
        if not isinstance(name, str) or not name.strip():
            raise ValueError("姓名必须是非空字符串")

        # 验证 age
        if not isinstance(age, int) or age <= 0:
            raise ValueError("年龄必须是正整数")

        # 验证 sex
        if sex not in ["男", "女"]:
            raise ValueError("性别必须是 '男' 或 '女'")

        # 验证 scores
        if not isinstance(scores, dict):
            raise ValueError("分数必须是字典")
        for subject, score in scores.items():
            if not isinstance(score, int):
                raise ValueError(f"科目 '{subject}' 的分数必须是整数")

        # 验证 hobbies
        if not isinstance(hobbies, list):
            raise ValueError("爱好必须是列表")
        for hobby in hobbies:
            if not isinstance(hobby, str):
                raise ValueError(f"爱好 '{hobby}' 必须是字符串")

        # 如果验证通过,调用原始函数
        return func(*args, **kwargs)

    return wrapper


@validate_student_info
def add_student_info(name, age, sex, scores, hobbies):
    """
    :param name: 姓名
    :param age: 年龄
    :param sex: 性别
    :param scores: 多个科目的考试分数 dict类型
    :param hobbies: 多个爱好 list类型
    :return: 结果
    """
    print("完成学生信息入库。")
    return "SUCCESS"

if __name__ == '__main__':
    # 正确的参数
    try:
        result = add_student_info("小李", 20, "女", {"Math": 90, "English": 85}, ["乒乓球", "音乐"])
        print(result)
    except ValueError as e:
        print(f"验证失败: {e}")

    # 错误的参数
    try:
        result = add_student_info("小张", 5, "男2", {"Math": 90, "English": 85}, ["排球", "篮球"])
        print(result)
    except ValueError as e:
        print(f"验证失败: {e}")

总结

        手动调用装饰器与上个案例一样:

if __name__ == '__main__':
    # 手动调用装饰器
    add_student_info = validate_student_info(add_student_info)
    # 正确的参数
    try:
        result = add_student_info("小李", 20, "女", {"Math": 90, "English": 85}, ["乒乓球", "音乐"])
        print(result)
    except ValueError as e:
        print(f"验证失败: {e}")

    # 错误的参数
    try:
        result = add_student_info("小张", 5, "男2", {"Math": 90, "English": 85}, ["排球", "篮球"])
        print(result)
    except ValueError as e:
        print(f"验证失败: {e}")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值