多余的位置参数、解包参数列表或元组以及扩展可迭代解包

5 投票
2 回答
2370 浏览
提问于 2025-04-16 00:58

这个问题可能会比较长,提前跟大家道个歉。

在Python中,我们可以在以下三种情况下使用星号(*):

I. 当我们定义一个函数,希望它能接受任意数量的参数时,比如在这个例子中:

def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

在这种情况下,多余的位置参数会被收集到一个叫做元组的东西里。

II. 反过来,如果参数已经在一个列表元组中,而我们想把它们拆开来作为单独的位置参数传给函数,比如在这个例子中:

>>> range(3, 6)             # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> range(*args)            # call with arguments unpacked from a list
[3, 4, 5]

III. 从Python 3开始,*还可以用在扩展列表元组拆包的场景中,比如在这个例子中处理元组:

>>> a, *b, c = range(5)
>>> b
[1, 2, 3]

或者处理列表:

>>> [a, *b, c] = range(5)
>>> b
[1, 2, 3]

在这两种情况下,从被拆包的可迭代对象中,所有没有被分配给任何必需表达式的项都会被放到一个列表里。

所以问题来了:在I的情况下,多余的参数被收集到一个元组中,而在III的情况下,多余的项则被放到了一个列表里。这种不一致是怎么回事呢?我找到的唯一解释是在PEP 3132中提到:

讨论过的可能的变化是:

[...]

把星号目标改成元组而不是列表。这将与函数的*args保持一致,但会让后续处理结果变得更困难。

不过,从教学的角度来看,这种不一致是个问题,尤其是如果你想处理结果的话,你总是可以说list(b)(假设上面的例子中b是一个元组)。我是不是漏掉了什么?

2 个回答

8

你漏掉一个了。

IV. 在Python 3中,参数列表里单独的一个*表示位置参数到此为止,之后的参数只能用关键字来传递,这叫做仅限关键字参数

def foo(a, b, *, key = None):
    pass

你可以这样调用:foo(1, 2, key = 3),但不能这样调用:foo(1, 2, 3)

7

在Python中,我们可以在以下三种情况下使用 *

你指的当然是前缀 *,因为中缀 * 是用来表示乘法的。

不过,从教学的角度来看,这种不一致性是个问题,特别是如果你想处理结果,你总是可以说 list(b)(假设上面的例子中 b 是一个元组)。我是不是漏掉了什么?

我觉得这个设计问题(已经存在很久了!)在于,当你接收任意参数时,它们是以元组的形式传递过来的,而在很多情况下,使用列表会更有用,并且没有什么真正的缺点(为了创建一个列表而需要的那点额外处理和内存,在函数调用的开销中几乎可以忽略不计——或者说在序列解包时也是如此;同时创建一个列表和一个元组所需的额外处理和内存其实更让人烦恼)。

你几乎不能用元组做的事情,列表都能做——基本上,只有在做哈希(用作集合项或字典键)时才会用到元组,而列表提供了更多的额外功能,不仅仅是为了修改它……像 countindex 这样的方法也很有用。

撰写回答