英文原文:All you need to know about Asterisks in Python
原文作者:bascodes
大多数开发人员都知道星号(*
)字符是 Python 中的乘法运算符:
1 | product = 4 * 2 # 8 |
但是,对于列表或字典数据结构而言,星号有一些特殊含义。
*args
和 **kwargs
函数定义中的 *args
星号操作符(也称为解构操作符)在 Python 中最广为人知的用法是在函数中使用。
假设有一个函数,它可以累加值并返回这些值的和:
1 | def add(number_1, number_2): |
如果你想对任意数量的值求和呢?可以在参数名称前添加星号,如这样:
1 | def add(*numbers): |
正如你可能已从函数体中注意到,我们期望 numbers
是一个可迭代对象。实际上,如果你检查其类型,numbers
是一个 tuple
。而且,实际上,我们现在可以用任意数量的参数调用该函数:
1 | add(1, 2, 3, 4) # 10 |
函数调用中的 *
到目前为止,我们已经知道可以定义一个函数,使它接受一系列参数。但是如果我们有一个函数,它有一组固定的参数,我们想要传递一系列值给它。考虑这个函数:
1 | def add(number_1, number_2, number_3): |
这个函数正好接受 3 个参数。假设我们有一个正好有三个元素的列表。当然,我们可以这样调用函数:
1 | my_list = [1, 2, 3] |
幸运的是,解构操作符(*
)可以以两种方式工作。我们已经在函数的定义中看到了它的用法,但是我们也可以使用它来调用函数:
1 | my_list = [1, 2, 3] |
函数定义中的 **kwargs
同样地,我们可以使用星号(*
)操作符来解构列表,我们也可以使用双星号(**
)操作符来解构 Python 函数中的字典。
考虑一个这样的函数:
1 | def change_user_details(username, email, phone, date_of_birth, street_address): |
如果使用关键字参数(kwargs
)调用它,函数调用可能如下所示:
1 | change_user_details('bascodes', email='blog@bascodes.example.com', phone='...', ...) |
在这种情况下,我们可以像这样重写函数,以接受任意数量的关键字参数,然后将这些参数表示为一个名为 kwargs
的字典:
1 | def change_user_details(username, **kwargs): |
当然,我们可以像使用 Python 中的任何其他字典一样使用 kwargs
字典,因此,如果我们像这样实际使用字典数据结构,那么函数可能会变得更清晰一些:
1 | def change_user_details(username, **kwargs): |
函数调用中的 **
当然,**
操作符也适用于调用函数:
1 | details = { |
限制如何调用函数
仅关键字参数
函数定义中星号最令人惊讶的特性之一是它可以单独使用,即不需要变量(参数)名。也就是说,这是 Python 中一个完全有效的函数定义:
1 | def my_function(*, keyword_arg_1): |
但是,在这种情况下,独立的星号有什么作用呢?如上所述,星号在列表中捕获所有(非关键字)参数。在我们的示例中,没有变量名可出现在参数列表中。在 *
之后我们有一个名为 keyword_arg_1
的变量。因为 *
已经匹配了所有的位置参数,我们剩下的 keyword_arg_1
,它必须用作关键字参数。
使用 my_function(1)
调用上面的函数会抛出一个错误:
1 | TypeError: my_function() takes 0 positional arguments but 1 was given |
仅位置参数
与上个示例中仅使用关键字参数相反,如果我们想强制函数的用户仅使用位置参数,该怎么办呢?
好吧,有一种非常 Pythonic 的方法。我们说明下使用 /
符号(与乘法相反)的处理技巧:
1 | def only_positional_arguments(arg1, arg2, /): |
令人惊讶的是,很少有 Python 开发人员知道这个通过 PEP 570 引入到 Python 3.8 的技巧。
如果你使用 only_positional_arguments(arg1=1, arg2=2)
调用上面的函数,将会抛出一个 TypeError:
1 | TypeError: only_positional_arguments() got some positional-only arguments passed as keyword arguments: 'arg1, arg2' |
在字面量中使用 *
和 **
星号(*
)和双星号(**
)操作符不仅适用于函数定义和调用,还可用于构造列表笔字典。
构造列表
假设,我们有两个列表,并想合并它们。
1 | my_list_1 = [1, 2, 3] |
当然,我们可以用 +
操作符合并这些列表:
1 | merged_list = my_list_1 + my_list_2 |
然而,*
操作符给了我们更多的灵活性。假设,我们想在中间插入一个标量值,我们可以使用:
1 | some_value = 42 |
构造字典
适用于列表和星号(*
)操作符的,同样适用于字典和双星号(**
)。
1 | social_media_details = { |
解构列表
你可能已经知道,可以像这样将列表的元素拆分为多个变量:
1 | my_list = [1, 2, 3] |
但是你知道吗,当你有一个任意长度的列表时,你可以使用星号(*
)来赋值给变量?
假设,你想要特定列表变量中的第一个和最后一个元素。
你可以像这样使用星号:
1 | my_list = [1, 2, 3] |
在这个示例中,a
现在饮食列表的第一个元素,c
包含 my_list
的最后一个元素。但是,b
中有什么呢?
在 b
中,有整个列表,除了第一个和最后一个元素,即 [2]
。注意,现在 b
是一个列表。
当我们研究更大的列表时,这个概念可能会更清晰一点:
1 | my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |