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

5

这个问题可能会比较长,所以我提前道歉。

在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)


谢谢。虽然并不是我的问题的答案,但将所有情况收集在一个帖子中还是很好的。 - Alexandros Gezerlis

7
在Python中,我们可以在以下三种情况下使用*
您当然是指前缀* -- 中缀*用于乘法。
然而,从教学的角度来看,这种不一致性是有问题的,特别是考虑到如果您想要处理结果,您可以总是说list(b)(假设上面的示例b是一个元组)。我错过了什么吗?
我会说设计问题(旧且非常老套!)在于当您接收任意参数时,您会将它们作为元组接收,而在许多情况下,列表可能更有用而没有真正的缺点(在函数调用开销的上下文中可能需要一点额外的处理和内存来制作一个列表而不是元组--或者序列解包,同样道理;制作列表以及元组所需的额外处理和内存实际上更麻烦)。
除了哈希(用作集合项或字典键)之外,元组可以做的事情很少,而列表提供了更多的额外功能,不仅仅是用于修改...例如,count和index方法也很有用。

谢谢你的回答。简而言之,我听到的是 case I 应该被设计成给出一个列表。 - Alexandros Gezerlis
@Alexandros,那确实是我的观点——但当然事后诸葛亮,那个决定是在什么时候做出的我不确定,但它已经在11年前的Python 1.5.2中了。 - Alex Martelli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接