1. *args
(可变位置参数)
*args
用于在函数定义中接收任意数量的位置参数。这些参数在函数内部被当作一个元组来处理。
python
def sum_numbers(*args):
return sum(args)
print(sum_numbers(1, 2, 3)) # 输出:6
print(sum_numbers(4, 5, 6, 7)) # 输出:22
在这个例子中,sum_numbers
函数可以接收任意数量的数字,并计算它们的和。
2. **kwargs
(可变关键字参数)
**kwargs
用于在函数定义中接收任意数量的关键字参数。这些参数在函数内部被当作一个字典来处理。
python
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="张三", age=30)
# 输出:
# name: 张三
# age: 30
在这个例子中,print_info
函数可以接收任意数量的关键字参数,并打印它们。
3. *
和 **
在函数调用时的作用
*
和 **
在函数调用时展开列表和字典的内容作为位置参数和关键字参数,下面是一个简单的例子:
python
def my_function(a, b, c, d=None, e=None):
print(a, b, c, d, e)
# 定义一个列表,包含将要传递给函数的位置参数
ls = [1, 2, 3]
# 定义一个字典,包含将要传递给函数的关键字参数
kwargs = {'d': 4, 'e': 5}
# 使用 * 展开列表为位置参数,** 展开字典为关键字参数
my_function(*ls, **kwargs)
在这个例子中,my_function
需要至少三个位置参数 a
, b
, c
,以及两个可选的关键字参数 d
和 e
。我们创建了一个列表 ls
,它包含了要传递给函数的前三个位置参数的值。我们还创建了一个字典 kwargs
,它包含了要传递给函数的关键字参数的值。
当我们调用 my_function(*ls, **kwargs)
时,*ls
会将列表 ls
展开为位置参数,即 1, 2, 3
,而 **kwargs
会将字典 kwargs
展开为关键字参数,即 d=4, e=5
。因此,函数调用等同于 my_function(1, 2, 3, d=4, e=5)
。
输出将会是:
1 2 3 4 5
这种方式在你不确定函数需要多少参数,或者你想在运行时动态地构建参数列表时特别有用。
4. 重中之重
python
# 定义函数 func1,接受三个参数:x, y, z
def func1(x, y, z):
# 打印出一段包含字符串 'func1>>>' 和这三个参数的信息
print('func1>>>', x, y, z)
# 定义函数 func2,它使用 *args 和 **kwargs 来接受任意数量的位置参数和关键字参数
def func2(*args, **kwargs):
# 调用 func1,并使用 * 和 ** 来展开 args 和 kwargs 中的参数
# 这样 func1 就能接收到传递给 func2 的位置参数和关键字参数了
func1(*args, **kwargs)
# 调用 func2 函数,传入两个位置参数 1 和 2,以及一个关键字参数 z=3
# 这个调用等同于 func1(1, 2, 3),因为位置参数和关键字参数都被正确传递了
func2(1, 2, z=3)
当 func2(1, 2, z=3)
被调用时:
*args
捕获了位置参数1
和2
。**kwargs
捕获了关键字参数z=3
。- 然后
func2
内部调用了func1
,并且通过*args
和**kwargs
展开了参数。 - 因此
func1
接收到了正确的参数值x=1
,y=2
,z=3
并执行了打印语句。
在这个例子中,func2
的定义展示了 Python 中参数传递的灵活性和强大功能。通过使用 *args
和 **kwargs
,func2
可以接受任意数量和类型的参数,并将它们正确地传递给 func1
。这种技术有几个重要的应用场景,使其成为编程中的"重中之重":
-
代码解耦 :
func2
不需要知道func1
的确切参数列表。这意味着如果func1
的签名改变了,只要新的签名仍然兼容传递的参数,func2
就不需要修改。这有助于减少代码之间的耦合,使代码更容易维护和扩展。 -
动态调用 :在某些情况下,你可能不知道在编写
func2
时会调用哪个函数(在这个例子中是func1
)。通过使用*args
和**kwargs
,func2
可以动态地将参数传递给在运行时确定的任何函数。 -
参数转发 :在复杂的函数调用链中,一个函数可能需要将接收到的参数原封不动地传递给另一个函数。
*args
和**kwargs
提供了一种简洁而有效的方式来转发这些参数,而无需显式地列出它们。 -
API 设计 :当设计库或框架时,使用
*args
和**kwargs
可以让你的 API 更加灵活和可扩展。用户可以传递他们需要的任何参数,而你的代码只需要关心处理这些参数的核心逻辑。 -
可扩展性 :在软件开发中,需求经常变化。使用
*args
和**kwargs
编写的函数更容易适应这些变化,因为它们可以接受新类型的参数而无需修改函数签名。
在这个例子中,func2(1, 2, z=3)
调用展示了如何同时使用位置参数(通过 *args
传递)和关键字参数(通过 **kwargs
传递)。这种混合使用非常常见,并且在处理复杂的函数调用和参数传递时非常有用。因此,理解并能够熟练运用这些技术对于成为一名高效的 Python 程序员来说是非常重要的。
实用的案例
装饰器
*args
和 **kwargs
在许多实际场景中都非常有用。下面是一个复杂的例子,它结合了 *args
和 **kwargs
来构建一个灵活的函数装饰器,该装饰器可以用于记录函数调用的信息。
首先,我们来定义一个装饰器 log_function_call
,它使用 *args
和 **kwargs
来接收任意数量和类型的参数,并记录函数调用的详细信息:
python
import functools
import datetime
def log_function_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 记录函数调用前的时间
start_time = datetime.datetime.now()
# 调用原始函数并记录返回值
result = func(*args, **kwargs)
# 记录函数调用后的时间
end_time = datetime.datetime.now()
# 获取函数参数名称和值
arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
arg_values = [repr(args[i]) for i in range(len(args))]
arg_values.extend(f"{k}={repr(v)}" for k, v in kwargs.items())
full_args = ", ".join(arg_values)
if arg_names:
full_args = f"({', '.join(arg_names)}) = ({full_args})"
# 输出日志信息
print(f"Function {func.__name__} {full_args} called at {start_time} and returned at {end_time}")
print(f"Execution time: {end_time - start_time}")
print(f"Result: {result}\n")
return result
return wrapper
现在,让我们使用这个装饰器来装饰一个简单的函数,并查看它的效果:
python
@log_function_call
def add(x, y, z=10):
return x + y + z
# 函数调用
print(add(1, 2)) # 位置参数
print(add(1, 2, z=20)) # 位置参数和关键字参数
print(add(y=2, x=1)) # 关键字参数
当你运行这段代码时,你会看到每个函数调用之前和之后的详细日志信息,包括函数名称、参数值、调用时间和执行时间。这个装饰器利用了 *args
和 **kwargs
的灵活性,使得它可以无需修改就能用于具有不同参数签名的多个函数。
python
Function add (x, y, z) = (1, 2) called at 2024-03-26 10:54:07.619490 and returned at 2024-03-26 10:54:07.619490
Execution time: 0:00:00
Result: 13
13
Function add (x, y, z) = (1, 2, z=20) called at 2024-03-26 10:54:07.619490 and returned at 2024-03-26 10:54:07.619490
Execution time: 0:00:00
Result: 23
23
Function add (x, y, z) = (y=2, x=1) called at 2024-03-26 10:54:07.619490 and returned at 2024-03-26 10:54:07.619490
Execution time: 0:00:00
Result: 13
13
这个例子展示了 *args
和 **kwargs
如何使代码更加通用和可重用。在实际开发中,类似的技巧可以用于创建中间件、插件系统、事件处理器等需要高度灵活性和可扩展性的场景。