Python进阶【魔法参数 *args 和 **kwargs】
本文最后更新于82 天前,其中的信息可能已经过时,如有错误请发送邮件到daoshilaoqi@qq.com

经常在装饰器中见到这两个参数,今天来详细学习下。

在 Python 里,“魔法参数” 一般指的是 *args 和 **kwargs,它们在函数定义时使用,能够让函数接收不定数量的参数,可以让我们的函数变的超级灵活。

要搞清楚下面以下重点:

  • 什么是位置参数(*args)和关键字参数(**kwargs
  • 如何结合使用*args**kwargs
  • 魔法参数的应用场景和优缺点

一、位置参数(*args)

数据类型:元组

概念:*args 用于接收不定数量的非关键字参数(位置参数)。在函数定义中,*是一个特殊的语法,它会将传入的多个位置参数打包成一个元组。(*是必须的,args可以自定义)

def sum_numbers(*args):
    print(type(args)) ##<class 'tuple'>
    total = 0
    for num in args:
        total += num
    return total

result = sum_numbers(1, 2, 3, 4, 5)
print(result)  ## 输出 15

二、关键字参数(**kwargs)

数据类型:字典

概念:**kwargs 用于接受不定数量的关键字参数。**是一个特殊的语法,它会将传入的多个关键字参数打包成一个字典(dictionary)。(**是必须的,kwargs可以自定义)

def print_info(**kwargs):
    print(type(kwargs )) ##<class 'dict'>
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="ZLS", age=28, city="ShangHai")

三、*args 和 **kwargs 一起使用

def example_function(arg1, arg2, *args, **kwargs):
    print(f"固定位置参数 arg1: {arg1}") # 1
    print(f"固定位置参数 arg2: {arg2}") # 2
    print(f"不定位置参数 args: {args}") # (3,4)
    print(f"不定关键字参数 kwargs: {kwargs}") # {'key1': 'value1', 'key2': 'value2'}

example_function(1, 2, 3, 4, key1="value1", key2="value2")

四、魔法参数应用场景

1. 当函数参数数量不确定(*args)

    当编写的函数需要处理不定数量的位置参数时,*args 可以派上用场。例如实现一个计算平均值的函数:

    def calculate_average(*args):
        if not args:
            return 0
        total = sum(args)
        return total / len(args)
    
    print(calculate_average(1, 2, 3, 4, 5))  # 输出 3.0

    2. 包装函数调用(*args)

    在封装函数时,可能需要将接收到的参数原封不动地传递给另一个函数。例如,有一个日志记录函数,它会在调用其他函数前后记录日志:

    def log_function_call(func):
        def wrapper(*args):
            print(f"Call function {func.__name__},参数: {args}")
            result = func(*args)
            print(f"{func.__name__} 函数调用结束")
            return result
        return wrapper
    
    @log_function_call
    def add_numbers(a, b):
        return a + b
    
    print(add_numbers(3, 5))

    在这个例子中,wrapper 函数使用 *args 接收传递给 add_numbers 函数的所有位置参数,并将它们传递给 add_numbers 函数进行实际计算。

    3. 传递配置参数(**kwargs)

    当函数需要接收多个可选的关键字参数作为配置时,**kwargs 是一个很好的选择。例如,创建一个绘制图形的函数,它可以根据不同的配置参数来画不同的图形:

    def draw_pattern(shape_type, **kwargs):
        print(f"draw {shape_type} type")
        for key, value in kwargs.items():
            print(f"{key}: {value}")
    
    draw_pattern("circle", color="red", radius=5)
    draw_pattern("rectangle", color="blue", width=10, height=20)

    在这个函数中,shape_type 是必需的参数,而 **kwargs 可以接收任意数量的关键字参数,用于指定图形的其他属性,如颜色、半径、宽度、高度等。

    4. 类的初始化方法(**kwargs)

    在类的 __init__ 方法中使用 **kwargs 可以让类的初始化更加灵活。例如,定义一个用户类,它可以根据不同的关键字参数来初始化用户的属性:

    class User:
        def __init__(self, name, **kwargs):
            self.name = name
            for key, value in kwargs.items():
                setattr(self, key, value)  #set attribute
    
    user1 = User("ZLS", age=28, city="ShangHai")
    print(user1.name)  # 输出 ZLS
    print(user1.age)   # 输出 28
    print(user1.city)  # 输出 ShangHai

    这里,__init__ 方法使用 **kwargs 接收除 name 之外的其他属性,通过 setattr 方法将这些属性动态地添加到用户对象中。

    5. 类的继承和方法重写(*args +**kwargs)

    在类的继承和方法重写中,*args 和 **kwargs 可以确保子类在调用父类方法时,能够正确传递所有参数。例如:

    class ParentClass:
        def some_method(self, *args, **kwargs):
            print("父类方法被调用")
            print(f"位置参数: {args}")
            print(f"关键字参数: {kwargs}")
    
    class ChildClass(ParentClass):
        def some_method(self, *args, **kwargs):
            print("子类方法被调用,先调用父类方法")
            super().some_method(*args, **kwargs)
    
    child = ChildClass()
    child.some_method(1, 2, key="value")

    在这个例子中,子类 ChildClass 重写了父类 ParentClass 的 some_method 方法,并在子类方法中使用 super().some_method(*args, **kwargs) 调用父类的方法,确保所有参数都能正确传递。

    6. 装饰器

    在编写通用的装饰器时,*args 和 **kwargs 可以确保装饰器能够处理任意函数的调用,无论该函数接收多少个位置参数和关键字参数。例如,一个用于计算函数执行时间的装饰器:

    import time
    
    def measure_time(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            print(f"{func.__name__} 函数执行时间: {end_time - start_time} 秒")
            return result
        return wrapper
    
    @measure_time
    def complex_function(a, b, c=3, d=4):
        time.sleep(1)
        return a + b + c + d
    
    print(complex_function(1, 2, d=5))

    在这个装饰器中,wrapper 函数使用 *args 和 **kwargs 接收传递给被装饰函数的所有参数,并将它们传递给被装饰函数进行实际调用。这样,该装饰器可以应用于任何函数,而不需要考虑函数的具体参数定义。

    五、优缺点

    优点:

    1. 灵活性高,可以处理不定数量的参数
    2. 代码复用性强,装饰器和包装函数
    3. 方便类的继承和扩展

    缺点:

    1. 降低代码的可读性,调用者很难从函数定义中直接看出需要传递哪些参数
    2. 调试难度增加
    作者:ZLS
    版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0协议
    转载请注明文章地址及作者哦~
    上一篇
    下一篇