16 KiB
Python 参数和 kwargs:去神秘化
*立即观看**本教程有真实 Python 团队创建的相关视频课程。和文字教程一起看,加深理解: Python args 和 kwargs:揭秘
有时候,当你查看 Python 中的函数定义时,你可能会发现它带有两个奇怪的参数: *args 和 **kwargs 。如果你想知道这些特殊的变量是什么,或者为什么你的 IDE 在 main() 中定义它们,那么这篇文章就是为你准备的。您将学习如何在 Python 中使用 args 和 kwargs 来增加函数的灵活性。
到文章结束,你就知道:
*args和**kwargs到底是什么意思- 如何在函数定义中使用
*args和**kwargs - 如何使用单个星号(
*)解包可重复项 - 如何使用两个星号(
**)解包字典
本文假设你已经知道如何定义 Python 函数和使用 T2 列表和字典。
免费奖励: 并学习 Python 3 的基础知识,如使用数据类型、字典、列表和 Python 函数。
向函数传递多个参数
*args 和 **kwargs 允许您向一个函数传递多个参数或关键字参数。考虑下面的例子。这是一个简单的函数,它接受两个参数并返回它们的和:
def my_sum(a, b):
return a + b
这个函数工作正常,但是它仅限于两个参数。如果您需要对不同数量的参数求和,而传递的参数的具体数量只能在运行时确定,该怎么办?创建一个函数,不管传递给它的整数有多少,它都可以对所有整数求和,这不是很好吗?
在函数定义中使用 Python args 变量
有几种方法可以将不同数量的参数传递给函数。对于有收藏经验的人来说,第一种方式通常是最直观的。你只需将所有参数的列表或集合传递给你的函数。所以对于my_sum(),你可以传递一个需要相加的所有整数的列表:
# sum_integers_list.py
def my_sum(my_integers):
result = 0
for x in my_integers:
result += x
return result
list_of_integers = [1, 2, 3]
print(my_sum(list_of_integers))
这个实现是可行的,但是无论何时调用这个函数,都需要创建一个参数列表来传递给它。这可能不太方便,尤其是如果您事先不知道应该进入列表的所有值。
这就是*args真正有用的地方,因为它允许您传递不同数量的位置参数。举以下例子:
# sum_integers_args.py
def my_sum(*args):
result = 0
# Iterating over the Python args tuple
for x in args:
result += x
return result
print(my_sum(1, 2, 3))
在本例中,您不再向my_sum()传递列表。相反,你要传递三个不同的位置参数。my_sum()获取输入中提供的所有参数,并将它们打包到一个名为args的可迭代对象中。
注意 **args只是一个名字。**你不需要使用args这个名字。你可以选择任何你喜欢的名字,比如integers:
# sum_integers_args_2.py
def my_sum(*integers):
result = 0
for x in integers:
result += x
return result
print(my_sum(1, 2, 3))
即使您将 iterable 对象作为integers而不是args传递,该函数仍然有效。这里重要的是你使用拆包操作员 ( *)。
请记住,使用解包操作符*得到的可迭代对象是,不是list,而是tuple 。一个tuple和一个list相似,它们都支持切片和迭代。然而,元组至少在一个方面非常不同:列表是可变的,而元组不是。要对此进行测试,请运行以下代码。这个脚本试图改变一个列表的值:
# change_list.py
my_list = [1, 2, 3]
my_list[0] = 9
print(my_list)
位于列表第一个索引处的值应更新为9。如果您执行这个脚本,您将看到列表确实被修改了:
$ python change_list.py
[9, 2, 3]
第一个值不再是0,而是更新后的值9。现在,试着对一个元组做同样的事情:
# change_tuple.py
my_tuple = (1, 2, 3)
my_tuple[0] = 9
print(my_tuple)
在这里,您可以看到相同的值,除了它们作为一个元组保存在一起。如果您尝试执行这个脚本,您将看到 Python 解释器返回一个错误:
$ python change_tuple.py
Traceback (most recent call last):
File "change_tuple.py", line 3, in <module>
my_tuple[0] = 9
TypeError: 'tuple' object does not support item assignment
这是因为 tuple 是一个不可变的对象,它的值在赋值后不能改变。在处理元组和*args时,请记住这一点。
在函数定义中使用 Python kwargs 变量
好了,现在你已经明白了*args是干什么的,但是**kwargs呢?**kwargs的工作方式与*args相似,但是它不接受位置参数,而是接受关键字(或名为的**)参数。举以下例子:**
# concatenate.py
def concatenate(**kwargs):
result = ""
# Iterating over the Python kwargs dictionary
for arg in kwargs.values():
result += arg
return result
print(concatenate(a="Real", b="Python", c="Is", d="Great", e="!"))
当您执行上面的脚本时,concatenate()将遍历 Python kwargs 字典并连接它找到的所有值:
$ python concatenate.py
RealPythonIsGreat!
和args一样,kwargs只是一个名字,想怎么改就怎么改。同样,这里重要的是使用拆包操作符 ( **)。
所以,前面的例子可以写成这样:
# concatenate_2.py
def concatenate(**words):
result = ""
for arg in words.values():
result += arg
return result
print(concatenate(a="Real", b="Python", c="Is", d="Great", e="!"))
注意,在上面的例子中,iterable 对象是一个标准的dict。如果您遍历字典并想要返回它的值,如示例所示,那么您必须使用.values()。
事实上,如果您忘记使用这个方法,您会发现自己正在遍历 Python kwargs 字典的键,如下例所示:
# concatenate_keys.py
def concatenate(**kwargs):
result = ""
# Iterating over the keys of the Python kwargs dictionary
for arg in kwargs:
result += arg
return result
print(concatenate(a="Real", b="Python", c="Is", d="Great", e="!"))
现在,如果您尝试执行这个示例,您会注意到以下输出:
$ python concatenate_keys.py
abcde
如您所见,如果您不指定.values(),您的函数将迭代您的 Python kwargs 字典的键,返回错误的结果。
函数中的参数排序
既然您已经了解了*args和**kwargs的用途,那么您就可以开始编写接受不同数量输入参数的函数了。但是,如果您想创建一个函数,同时接受可变数量的位置和命名参数,该怎么办呢?
在这种情况下,你必须记住订单计数。正如非默认参数必须在默认参数之前一样,*args必须在**kwargs之前。
概括地说,参数的正确顺序是:
- 标准参数
*args论据**kwargs论据
例如,这个函数定义是正确的:
# correct_function_definition.py
def my_function(a, b, *args, **kwargs):
pass
*args变量适当地列在**kwargs之前。但是如果你试图改变参数的顺序呢?例如,考虑以下函数:
# wrong_function_definition.py
def my_function(a, b, **kwargs, *args):
pass
现在,在函数定义中,**kwargs在*args之前。如果您尝试运行这个例子,您将从解释器收到一个错误:
$ python wrong_function_definition.py
File "wrong_function_definition.py", line 2
def my_function(a, b, **kwargs, *args):
^
SyntaxError: invalid syntax
在这种情况下,由于*args在**kwargs之后,Python 解释器抛出一个 SyntaxError 。
带星号的拆包符:* & **
现在,您可以使用*args和**kwargs来定义接受不同数量输入参数的 Python 函数。让我们更深入地了解一下开箱操作员。
Python 2 中引入了单星和双星号解包操作符。在 3.5 版本中,由于 PEP 448 ,它们变得更加强大。简而言之,解包操作符是从 Python 中的 iterable 对象解包值的操作符。单星号操作符*可以用在 Python 提供的任何 iterable 上,而双星号操作符**只能用在字典上。
让我们从一个例子开始:
# print_list.py
my_list = [1, 2, 3]
print(my_list)
这段代码定义了一个列表,然后将它打印到标准输出中:
$ python print_list.py
[1, 2, 3]
请注意列表是如何打印的,以及相应的括号和逗号。
现在,尝试将解包操作符*添加到列表名称的前面:
# print_unpacked_list.py
my_list = [1, 2, 3]
print(*my_list)
这里,*操作符告诉print()首先解包列表。
在这种情况下,输出不再是列表本身,而是列表的内容:
$ python print_unpacked_list.py
1 2 3
你能看出这次执行和print_list.py的区别吗?print()没有使用列表,而是使用了三个独立的参数作为输入。
您会注意到的另一件事是,在print_unpacked_list.py中,您使用解包操作符*来调用函数,而不是在函数定义中。在这种情况下,print()接受列表中的所有项目,就像它们是单个参数一样。
您也可以使用这个方法来调用您自己的函数,但是如果您的函数需要特定数量的参数,那么您解包的 iterable 必须具有相同数量的参数。
要测试此行为,请考虑以下脚本:
# unpacking_call.py
def my_sum(a, b, c):
print(a + b + c)
my_list = [1, 2, 3]
my_sum(*my_list)
这里,my_sum()明确声明a、b和c是必需参数。
如果您运行这个脚本,您将得到my_list中三个数字的总和:
$ python unpacking_call.py
6
my_list中的 3 个元素与my_sum()中所需的参数完全匹配。
现在看看下面的脚本,其中my_list有 4 个参数,而不是 3 个:
# wrong_unpacking_call.py
def my_sum(a, b, c):
print(a + b + c)
my_list = [1, 2, 3, 4]
my_sum(*my_list)
在这个例子中,my_sum()仍然只需要三个参数,但是*操作符从列表中得到 4 个条目。如果您尝试执行这个脚本,您将会看到 Python 解释器无法运行它:
$ python wrong_unpacking_call.py
Traceback (most recent call last):
File "wrong_unpacking_call.py", line 6, in <module>
my_sum(*my_list)
TypeError: my_sum() takes 3 positional arguments but 4 were given
当您使用*操作符解包一个列表并将参数传递给一个函数时,就好像您在单独传递每个参数一样。这意味着您可以使用多个解包操作符从几个列表中获取值,并将它们全部传递给一个函数。
要测试此行为,请考虑以下示例:
# sum_integers_args_3.py
def my_sum(*args):
result = 0
for x in args:
result += x
return result
list1 = [1, 2, 3]
list2 = [4, 5]
list3 = [6, 7, 8, 9]
print(my_sum(*list1, *list2, *list3))
如果您运行这个例子,所有三个列表都将被解包。每个单独的项目被传递到my_sum(),产生以下输出:
$ python sum_integers_args_3.py
45
解包操作符还有其他方便的用途。例如,假设您需要将一个列表分成三个不同的部分。输出应该显示第一个值、最后一个值以及两者之间的所有值。使用解包操作符,只需一行代码就可以完成:
# extract_list_body.py
my_list = [1, 2, 3, 4, 5, 6]
a, *b, c = my_list
print(a)
print(b)
print(c)
本例中my_list包含 6 项。第一个变量分配给a,最后一个分配给c,所有其他值都被打包到一个新的列表b中。如果您运行脚本,print()将向您显示,您的三个变量具有您所期望的值:
$ python extract_list_body.py
1
[2, 3, 4, 5]
6
使用解包操作符*可以做的另一件有趣的事情是拆分任何可迭代对象的项目。如果您需要合并两个列表,这可能非常有用,例如:
# merging_lists.py
my_first_list = [1, 2, 3]
my_second_list = [4, 5, 6]
my_merged_list = [*my_first_list, *my_second_list]
print(my_merged_list)
解包操作符*被加在my_first_list和my_second_list的前面。
如果您运行这个脚本,您会看到结果是一个合并的列表:
$ python merging_lists.py
[1, 2, 3, 4, 5, 6]
您甚至可以使用解包操作符**合并两个不同的字典:
# merging_dicts.py
my_first_dict = {"A": 1, "B": 2}
my_second_dict = {"C": 3, "D": 4}
my_merged_dict = {**my_first_dict, **my_second_dict}
print(my_merged_dict)
这里,要合并的 iterables 是my_first_dict和my_second_dict。
执行这段代码会输出一个合并的字典:
$ python merging_dicts.py
{'A': 1, 'B': 2, 'C': 3, 'D': 4}
记住*操作符作用于任何可迭代对象。它也可以用来打开管柱:
# string_to_list.py
a = [*"RealPython"]
print(a)
在 Python 中,字符串是可迭代的对象,所以*会将其解包,并将所有单个值放在一个列表a中:
$ python string_to_list.py
['R', 'e', 'a', 'l', 'P', 'y', 't', 'h', 'o', 'n']
前面的例子看起来很棒,但是当你使用这些操作符时,记住第七条规则很重要,Tim Peters 的Python 的禅宗:可读性很重要。
要了解原因,请考虑以下示例:
# mysterious_statement.py
*a, = "RealPython"
print(a)
有一个解包操作符*,后面跟着一个变量、一个逗号和一个赋值。一条线装了这么多东西!实际上,这段代码与前面的例子没有什么不同。它只接受字符串RealPython并将所有条目分配给新列表a,这要感谢拆包操作符*。
a后面的逗号起了作用。将解包操作符用于变量赋值时,Python 要求结果变量要么是列表,要么是元组。通过后面的逗号,您定义了一个只有一个命名变量a的元组,这个变量就是列表['R', 'e', 'a', 'l', 'P', 'y', 't', 'h', 'o', 'n']。
您永远看不到 Python 在这个操作中创建的元组,因为您将元组解包与解包操作符*结合使用。
如果在赋值的左边命名第二个变量,Python 会将字符串的最后一个字符赋给第二个变量,同时收集列表中所有剩余的字符a:
>>> *a, b = "RealPython"
>>> b
"n"
>>> type(b)
<class 'str'>
>>> a
["R", "e", "a", "l", "P", "y", "t", "h", o"]
>>> type(a)
<class 'list'>
如果您以前使用过元组解包,那么当您对第二个命名变量使用该操作时,结果可能会更熟悉,如上所示。但是,如果您想将可变长度 iterable 的所有项解包到一个变量a中,那么您需要添加逗号(, ) ,而不需要命名第二个变量。然后 Python 会将所有项目解包到第一个命名变量中,这是一个列表。
虽然这是一个巧妙的技巧,但许多 python 爱好者并不认为这段代码可读性很强。因此,最好少用这种结构。
结论
您现在可以使用 *args 和 **kwargs 在函数中接受可变数量的参数。您还了解了更多关于解包操作符的知识。
你已经学会了:
*args和**kwargs到底是什么意思- 如何在函数定义中使用
*args和**kwargs - 如何使用单个星号(
*)解包可重复项 - 如何使用两个星号(
**)解包字典
如果你还有问题,不要犹豫,在下面的评论区联系我们!要了解 Python 中星号用法的更多信息,请看一下 Trey Hunner 关于这个主题的文章。
立即观看本教程有真实 Python 团队创建的相关视频课程。和文字教程一起看,加深理解: Python args 和 kwargs:揭秘**