PEP 448 – 扩展解包通用化
- 作者:
- Joshua Landau <joshua at landau.ws>
- 讨论组:
- Python-Ideas 邮件列表
- 状态:
- 最终
- 类型:
- 标准轨迹
- 创建日期:
- 2013-06-29
- Python 版本:
- 3.5
- 历史记录:
摘要
本 PEP 提出扩展使用 *
可迭代解包运算符和 **
字典解包运算符,以允许在更多位置、任意次数以及在函数调用和显示中进行解包。
函数调用被提议支持任意数量的解包,而不仅仅是一个。
>>> print(*[1], *[2], 3)
1 2 3
>>> dict(**{'x': 1}, y=2, **{'z': 3})
{'x': 1, 'y': 2, 'z': 3}
解包被提议允许在元组、列表、集合和字典显示中使用。
>>> *range(4), 4
(0, 1, 2, 3, 4)
>>> [*range(4), 4]
[0, 1, 2, 3, 4]
>>> {*range(4), 4}
{0, 1, 2, 3, 4}
>>> {'x': 1, **{'y': 2}}
{'x': 1, 'y': 2}
在字典中,后面的值将始终覆盖前面的值。
>>> {'x': 1, **{'x': 2}}
{'x': 2}
>>> {**{'x': 2}, 'x': 1}
{'x': 1}
本 PEP 不包括在列表、集合和字典推导中使用解包运算符,尽管这尚未被排除在未来的提案之外。
理由
目前 *
可迭代解包运算符的使用存在不必要的限制,会损害可读性。
多次解包有明显的理由。当你想将多个可迭代对象解包到函数定义中,或者在解包之后跟上更多位置参数,最自然的方式应该是编写
function(**kw_arguments, **more_arguments)
function(*arguments, argument)
这在 print
和 str.format
等简单示例中非常有用。相反,你可能会被迫编写
kwargs = dict(kw_arguments)
kwargs.update(more_arguments)
function(**kwargs)
args = list(arguments)
args.append(arg)
function(*args)
或者,如果你知道怎么做
from collections import ChainMap
function(**ChainMap(more_arguments, arguments))
from itertools import chain
function(*chain(args, [arg]))
这会增加不必要的行噪声,并且,使用第一种方法会导致重复的工作。
在容器内部进行解包有两个主要理由。首先,赋值存在对称性,其中 fst, *other, lst = elems
和 elems = fst, *other, lst
是近似逆运算,忽略了类型的具体细节。这实际上通过消除特殊情况简化了语言。
其次,它极大地简化了“添加”类型,例如合并字典,并且以一种明确且定义明确的方式进行。
combination = {**first_dictionary, "x": 1, "y": 2}
而不是
combination = first_dictionary.copy()
combination.update({"x": 1, "y": 2})
这在表达式优先的上下文中尤其重要。这也是将可迭代对象累加到列表中的一种更易读的方式,例如 my_list + list(my_tuple) + list(my_range)
,现在等同于 [*my_list, *my_tuple, *my_range]
。
规范
函数调用可以接受无限数量的 *
和 **
解包。位置参数与 *
解包的顺序没有限制,关键字参数与 **
解包的顺序也没有限制。
函数调用仍然存在关键字参数必须在位置参数之后,**
解包必须在 *
解包之后。
目前,如果一个参数被多次给出 - 例如,一个位置参数既以位置形式给出,又以关键字形式给出 - 则会引发 TypeError
。对于通过多个 **
解包提供的重复参数,这种情况仍然适用,例如 f(**{'x': 2}, **{'x': 3})
,只是错误会在运行时被检测到。
函数看起来像这样
function(
argument or *args, argument or *args, ...,
kwargument or *args, kwargument or *args, ...,
kwargument or **kwargs, kwargument or **kwargs, ...
)
元组、列表、集合和字典允许解包。这将如同从解包项目中提取的元素按照解包位置的顺序插入,类似于函数调用中的解包方式。字典需要 **
解包;所有其他都需要 *
解包。
字典中的键保持从右到左的优先顺序,因此 {**{'a': 1}, 'a': 2, **{'a': 3}}
评估为 {'a': 3}
。解包的数量和位置没有限制。
缺点
函数调用中参数的允许顺序比以前更复杂。对规则的最简单解释可能是“位置参数在关键字参数和 **
解包之前;*
解包在 **
解包之前”。
虽然 *elements, = iterable
会导致 elements
为列表,但 elements = *iterable,
会导致 elements
为元组。这背后的原因可能会让不熟悉这种构造的人感到困惑。
人们对字典中允许重复键,但在函数调用语法中引发错误,感到担忧。虽然这在当前语法中已经是这样了,但本提案可能会加剧这个问题。这在实践中到底有多大问题还有待观察。
变体
PEP 最初考虑过是否可以放宽函数调用中参数类型的排序(位置、关键字、*
或 **
)。这没有得到多少支持,因此这个想法被搁置了。
本 PEP 的早期版本允许在列表、集合和字典推导中使用解包运算符,作为容器可迭代对象的扁平化运算符。
>>> ranges = [range(i) for i in range(5)]
>>> [*item for item in ranges]
[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]
>>> {*item for item in ranges}
{0, 1, 2, 3}
这引起了关于可读性的强烈担忧和温和的支持。为了不影响 PEP 中不太有争议的部分,这没有与提案的其余部分一起被接受。
函数调用中的无括号推导,例如 f(x for x in it)
,已经是有效的。这些可以扩展到
f(*x for x in it) == f((*x for x in it))
f(**x for x in it) == f({**x for x in it})
但是,目前还不清楚这是否是最佳行为,或者它是否应该解包到对 f
的调用的参数中。由于这很可能会让人感到困惑,并且效用也很小,因此本 PEP 中不包含它。相反,这些将抛出 SyntaxError
,应该使用带显式括号的推导。
批准
本 PEP 于 2015 年 2 月 25 日被 Guido 接受 [1].
实现
Python 3.5 的实现可以在 bug 跟踪器上的 Issue 2292 中找到 [2]。目前,这包括对推导内部解包的支持,应该将其删除。
参考文献
[3] Python-ideas 邮件列表中的讨论,“列表/数组推导扩展”,Alexander Heger (https://mail.python.org/pipermail/python-ideas/2011-December/013097.html)
版权
本文件已进入公有领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0448.rst
最后修改时间: 2023-12-11 18:58:32 GMT