15 函数及装饰器的用法

一、函数基本结构:

def 函数名(参数列表):
    函数体
    return 返回值

二、参数类型顺序

  • 位置参数必须在默认参数之前:这是因为位置参数没有默认值,而默认参数有,所以位置参数需要先被解析。
  • *args 应该在位置参数和默认参数之后:因为 *args 收集所有额外的位置参数,它应该出现在所有普通位置参数之后。
  • **kwargs 应该在最后:因为它收集所有的额外关键字参数,自然应该放在所有其他参数之后。
def func(位置参数, 默认参数, *args, **kwargs):
    pass

三、装饰器

本质上是一个函数,可以让我们在不修改原函数的情况下,为其增加新的功能,就像给房子装修一样,不改变房子的结构,但是增加了新的功能

 1、工作原理:

  • 装饰器是一个函数
  • 它接收一个函数作为参数
  • 在内部定义一个新函数来包装原函数
  • 返回这个新函数
"""
一个最经典的装饰器示例
功能:记录函数的执行时间
"""

import time# 用于记录时间
import functools  # functools 模块提供了一些用于高阶函数的工具,包括 wraps 装饰器

def timer(func):# 装饰器函数,用于记录函数的执行时间
    @functools.wraps(func)  # 保留原函数的信息(名称、文档等)
    def wrapper(*args, **kwargs): # 包装函数,用于在函数执行前后添加日志 *args,**kwargs表示可以接受任意数量的位置参数和关键字参数
        # 1. 装饰器要做的事情:记录开始时间
        start_time = time.time()
        
        # 2. 执行被装饰的原函数
        result = func(*args, **kwargs)
        
        # 3. 装饰器要做的事情:计算执行时间
        end_time = time.time()
        print(f'函数 {func.__name__} 执行时间:{end_time - start_time:.2f} 秒')
        
        # 4. 返回原函数的结果
        return result
    
    return wrapper

# 使用装饰器
@timer
def slow_function(sleep_time):
    """这是一个测试函数"""
    time.sleep(sleep_time)# 睡眠sleep_time秒
    return "完成"
@timer
def add(a,b):
    return a+b

# 测试
if __name__ == "__main__":
    print("开始测试...")
    result = slow_function(1)  # 睡眠1秒
    print(f"函数返回值: {result}")
    print(f"函数名称: {slow_function.__name__}")  # 因为用了 wraps,这里会显示 'slow_function' 而不是 'wrapper' 
            
    print("--------------------------------")
    result2=add(1,2)
    print(f"函数返回值: {result2}")

2、记忆要点:

  • 装饰器就是一个函数包装器
  • 执行顺序:装饰器外层代码 -> 被装饰函数 -> 装饰器内层代码
  • 装饰器在定义时就会执行,而不是在调用时

详细执行顺序如下:

def my_decorator(func):
    print("1. 装饰器外层代码:定义时就执行")  # 第1步执行
    
    def wrapper():
        print("3. 装饰器内层代码:函数调用前")  # 第3步执行
        func()                                  # 第4步执行
        print("5. 装饰器内层代码:函数调用后")  # 第5步执行
    
    print("2. 装饰器外层代码:返回wrapper前")   # 第2步执行
    return wrapper

@my_decorator
def hello():
    print("4. 被装饰函数执行")                  # 第4步执行

print("开始调用函数")
hello()
print("函数调用结束")

执行结果:

1. 装饰器外层代码:定义时就执行
2. 装饰器外层代码:返回wrapper前
开始调用函数
3. 装饰器内层代码:函数调用前
4. 被装饰函数执行
5. 装饰器内层代码:函数调用后
函数调用结束

3、执行顺序总结:

"""
演示装饰器的执行顺序
"""

def my_decorator(func):
    print("1. 装饰器外层代码:定义时就执行")
    
    def wrapper():
        print("3. 装饰器内层代码:函数调用前")
        func()
        print("5. 装饰器内层代码:函数调用后")
    
    print("2. 装饰器外层代码:返回wrapper前")
    return wrapper

@my_decorator
def hello():
    print("4. 被装饰函数执行")

print("\n=== 函数定义已完成 ===")
print("开始调用函数")
hello()
print("函数调用结束") 

执行结果:
1. 装饰器外层代码:定义时就执行
2. 装饰器外层代码:返回wrapper前

=== 函数定义已完成 ===
开始调用函数
3. 装饰器内层代码:函数调用前
4. 被装饰函数执行
5. 装饰器内层代码:函数调用后
函数调用结束

详细解释顺序:

1. 定义阶段(程序加载时)

当Python解释器读到 @my_decorator 时:

  • 执行装饰器外层代码(第1行输出)
  • 定义内部的 wrapper 函数(但还不执行)
  • 执行 return wrapper 前的代码(第2行输出)
  • 将 hello 函数替换为 wrapper 函数

2. 调用阶段(执行 hello() 时)

当执行 hello() 时,实际上是在执行 wrapper():

  • 执行装饰器内层代码(第3行输出)
  • 调用原始的 hello 函数(第4行输出)
  • 执行装饰器内层代码(第5行输出)

其他基本函数定义详解:

"""
Python函数定义详解
目的:展示Python中函数定义的各种方式、参数传递和使用技巧
"""


#######################
# 1. 基本函数定义      #
#######################
def basic_function_demo():
    print("1. 基本函数定义示例")
    print("-" * 50)

    # 1.1 最简单的函数 - 无参数无返回值
    def say_hello():
        print("Hello, World!")

    # 1.2 带参数的函数
    def greet(name):
        print(f"Hello, {name}!")

    # 1.3 带返回值的函数
    def add(a, b):
        return a + b

    # 1.4 多返回值函数
    def get_person_info():
        name = "张三"
        age = 25
        city = "北京"
        return name, age, city  # 返回元组

    # 测试这些函数
    print("基本函数调用:")
    say_hello()
    greet("小明")
    print(f"1 + 2 = {add(1, 2)}")
    name, age, city = get_person_info()  # 解包返回值
    print(f"个人信息:{name}, {age}岁, 来自{city}")


#######################
# 2. 参数类型          #
#######################
def parameter_types_demo():
    print("\n2. 参数类型示例")
    print("-" * 50)

    # 2.1 位置参数
    def position_args(name, age):
        print(f"姓名:{name}, 年龄:{age}")

    # 2.2 默认参数
    def default_args(name, age=18, city="北京"):
        print(f"姓名:{name}, 年龄:{age}, 城市:{city}")

    # 2.3 可变参数(*args)
    def variable_args(
        *args,
    ):  # *args 表示可变参数,可以传入任意数量的参数,这些参数会被打包成一个元组
        print(f"收到的参数:{args}")
        for arg in args:
            print(f"参数值:{arg}")

    # 2.4 关键字参数(**kwargs)
    def keyword_args(
        **kwargs,
    ):  # **kwargs 表示关键字参数,可以传入任意数量的参数,这些参数会被打包成一个字典
        print(f"收到的参数:{kwargs}")
        for key, value in kwargs.items():
            print(f"{key}: {value}")

    # 2.5 混合使用各种参数
    def mixed_args(
        name, age=18, *args, **kwargs
    ):  # 混合使用各种参数,name,age作为位置参数,*args作为可变参数,**kwargs作为关键字参数
        print(f"姓名:{name}")
        print(f"年龄:{age}")
        print(f"可变参数:{args}")
        print(f"关键字参数:{kwargs}")

    # 测试这些函数
    print("不同参数类型的使用:")
    position_args("小明", 20)
    default_args("小红")  # 使用默认参数
    default_args("小李", 25, "上海")  # 覆盖默认参数
    variable_args(1, 2, 3, "hello")  # 可变参数
    keyword_args(
        name="小王", age=22, city="广州"
    )  # 关键字参数,name,age,city作为键,小王,22,广州作为值 ; 使用名称=值的形式传参
    mixed_args("小张", 20, 1, 2, 3, city="深圳", hobby="编程")  # 混合参数


#######################
# 3. 参数检查          #
#######################
def parameter_checking_demo():
    print("\n3. 参数检查示例")
    print("-" * 50)

    # 3.1 基本类型检查
    def check_type(value: int) -> bool:
        """
        检查参数是否为整数
        :param value: 要检查的值
        :return: 是否为整数
        """
        if not isinstance(value, int):
            raise TypeError(f"参数必须是整数,而不是{type(value)}")
        return True

    # 3.2 值范围检查
    def check_age(age: int) -> None:
        """
        检查年龄是否合法
        :param age: 年龄
        :raises ValueError: 年龄不在有效范围内
        """
        if not 0 <= age <= 150:
            raise ValueError(f"年龄必须在0-150之间,当前值:{age}")

    # 3.3 参数默认值检查
    def connect_database(host="localhost", port=3306, timeout=30):
        """
        检查数据库连接参数
        """
        if not isinstance(port, int):
            raise TypeError("端口必须是整数")
        if not 0 < port < 65536:
            raise ValueError("端口必须在1-65535之间")
        if timeout <= 0:
            raise ValueError("超时时间必须大于0")

    # 测试参数检查
    print("参数检查示例:")
    try:
        check_type("123")  # 应该抛出TypeError
    except TypeError as e:
        print(f"类型检查错误:{e}")

    try:
        check_age(200)  # 应该抛出ValueError
    except ValueError as e:
        print(f"值检查错误:{e}")

    try:
        connect_database(port="8080")  # 应该抛出TypeError
    except TypeError as e:
        print(f"参数检查错误:{e}")


#######################
# 4. 空函数和装饰器    #
#######################
def empty_and_decorator_demo():
    print("\n4. 空函数和装饰器示例")
    print("-" * 50)

    # 4.1 空函数
    def pass_demo():
        pass  # 空函数,什么都不做

    # 4.2 简单装饰器
    def log_decorator(func):  # 装饰器,用于在函数调用前后添加日志
        def wrapper(*args, **kwargs):
            print(f"调用函数:{func.__name__}")
            result = func(*args, **kwargs)
            print(f"函数调用完成:{func.__name__}")
            return result

        return wrapper

    # 使用装饰器
    @log_decorator
    def test_function(x, y):
        return x + y

    # 测试空函数和装饰器
    print("空函数和装饰器示例:")
    pass_demo()  # 不做任何事
    result = test_function(10, 20)  # 带日志的函数调用
    print(f"计算结果:{result}")


#######################
# 5. 函数使用技巧      #
#######################
def function_tips():
    print("\n5. 函数使用技巧")
    print("-" * 50)

    print("1) 函数命名规范:")
    print("   - 使用小写字母和下划线")
    print("   - 动词开头:get_xxx, set_xxx, is_xxx")
    print("   - 见名知义:calculate_total而不是calc")

    print("\n2) 参数设计原则:")
    print("   - 参数数量不要太多(建议不超过5个)")
    print("   - 必选参数在前,可选参数在后")
    print("   - 灵活使用*args和**kwargs")

    print("\n3) 返回值建议:")
    print("   - 保持返回值类型一致")
    print("   - 使用元组返回多个值")
    print("   - 错误时使用异常而不是返回特殊值")

    print("\n4) 文档和注释:")
    print("   - 使用文档字符串说明功能")
    print("   - 注释复杂的逻辑")
    print("   - 说明参数类型和返回值")


# 运行所有示例
if __name__ == "__main__":
    basic_function_demo()
    parameter_types_demo()
    parameter_checking_demo()
    empty_and_decorator_demo()
    function_tips()

打印结果

1. 基本函数定义示例
--------------------------------------------------
基本函数调用:
Hello, World!
Hello, 小明!
1 + 2 = 3
个人信息:张三, 25岁, 来自北京

2. 参数类型示例
--------------------------------------------------
不同参数类型的使用:
姓名:小明, 年龄:20
姓名:小红, 年龄:18, 城市:北京
姓名:小李, 年龄:25, 城市:上海
收到的参数:(1, 2, 3, 'hello')
参数值:1
参数值:2
参数值:3
参数值:hello
收到的参数:{'name': '小王', 'age': 22, 'city': '广州'}
name: 小王
age: 22
city: 广州
姓名:小张
年龄:20
可变参数:(1, 2, 3)
关键字参数:{'city': '深圳', 'hobby': '编程'}

3. 参数检查示例
--------------------------------------------------
参数检查示例:
类型检查错误:参数必须是整数,而不是<class 'str'>
值检查错误:年龄必须在0-150之间,当前值:200
参数检查错误:端口必须是整数

4. 空函数和装饰器示例
--------------------------------------------------
空函数和装饰器示例:
调用函数:test_function
函数调用完成:test_function
计算结果:30

5. 函数使用技巧
--------------------------------------------------
1) 函数命名规范:
   - 使用小写字母和下划线
   - 动词开头:get_xxx, set_xxx, is_xxx
   - 见名知义:calculate_total而不是calc

2) 参数设计原则:
   - 参数数量不要太多(建议不超过5个)
   - 必选参数在前,可选参数在后
   - 灵活使用*args和**kwargs

3) 返回值建议:
   - 保持返回值类型一致
   - 使用元组返回多个值
   - 错误时使用异常而不是返回特殊值

4) 文档和注释:
   - 使用文档字符串说明功能
   - 注释复杂的逻辑
   - 说明参数类型和返回值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值