经常在装饰器中见到这两个参数,今天来详细学习下。
在 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
接收传递给被装饰函数的所有参数,并将它们传递给被装饰函数进行实际调用。这样,该装饰器可以应用于任何函数,而不需要考虑函数的具体参数定义。
五、优缺点
优点:
- 灵活性高,可以处理不定数量的参数
- 代码复用性强,装饰器和包装函数
- 方便类的继承和扩展
缺点:
- 降低代码的可读性,调用者很难从函数定义中直接看出需要传递哪些参数
- 调试难度增加