开箱概括

Unpacking generalizations

PEP 448 -- Additional Unpacking Generalizations 允许:

>>> LOL = [[1, 2], ['three']]
>>> [*LOL[0], *LOL[1]]
[1, 2, 'three']

好的!再见itertools.chain。反正一直不太喜欢你。

>>> [*L for L in LOL]
  File "<ipython-input-21-e86d2c09c33f>", line 1
    [*L for L in LOL]
    ^
SyntaxError: iterable unpacking cannot be used in comprehension

。为什么我们不能拥有美好的东西?

不幸的是,它们都存在语法错误:

[*l for l in lists]    # for l in lists: result.extend(l)
{*s for s in sets}     # for s in sets: result.update(s)
{**d for d in dicts}   # for d in dicts: result.update(d)
(*g for g in gens)     # for g in gens: yield from g

在理解中解包似乎是显而易见的和 Pythonic 的,并且从 shorthand“for-loop & append”到“for-loop & extend”有一个非常自然的扩展。

但是由于他们不厌其烦地添加了那个特殊的错误消息,所以大概有一个禁用它的原因。那么,该语法有什么问题?

这是简要在介绍解包概括的PEP 448中解释的:

Earlier iterations of this PEP allowed unpacking operators inside list, set, and dictionary comprehensions as a flattening operator over iterables of containers:

>>> 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}

This was met with a mix of strong concerns about readability and mild support. In order not to disadvantage the less controversial aspects of the PEP, this was not accepted with the rest of the proposal.

不过,这可能会在未来发生变化:

This PEP does not include unpacking operators inside list, set and dictionary comprehensions although this has not been ruled out for future proposals.


PEP 提到“对可读性的强烈担忧”。我不知道整个故事,但可以在邮件列表中找到导致做出此决定的详细讨论:

如果在列表理解中允许解包泛化,这是一个模棱两可的例子:

[*t for t in [(1, 'a'), (2, 'b'), (3, 'c')]]

According to one of the core developers,如果结果是 [1, 'a', 2, 'b', 3, 'c'] 而不是 [(1, 'a'), (2, 'b'), (3, 'c')].

会令人惊讶

由于没有正式的共识,所以不允许这些特殊情况更简单。

引自the Py-Dev mailing list thread in which this feature was accepted

So that leaves comprehensions. IIRC, during the development of the patch we realized that f(*x for x in xs) is sufficiently ambiguous that we decided to disallow it -- note that f(x for x in xs) is already somewhat of a special case because an argument can only be a "bare" generator expression if it is the only argument. The same reasoning doesn't apply (in that form) to list, set and dict comprehensions -- while f(x for x in xs) is identical in meaning to f((x for x in xs)), [x for x in xs] is NOT the same as [(x for x in xs)] (that's a list of one element, and the element is a generator expression)

(强调我的)

我还查看了此功能的 Python 问题跟踪器。我发现了一个问题,其中在实施时进行了讨论。帮助他们实现这一目标的消息序列从 here with a nice overview of the ambiguity introduced presented in msg234766 GvR 开始。

fear of link-rot 中,我在此处附上(格式化的)消息:

So I think the test function here should be:

def f(*a, **k): print(list(a), list(k))

Then we can try things like:

f(x for x in ['ab', 'cd'])

which prints a generator object, because this is interpreted as an argument that's a generator expression.

But now let's consider:

f(*x for x in ['ab', 'cd'])

I personally expected this to be equivalent to:

f(*'ab', *'cd')

IOW:

 f('a', 'b', 'c', 'd')

The PEP doesn't give clarity on what to do here. The question now is, should we interpret things like *x for x in ... as an extended form of generator expression, or as an extended form of *arg? I somehow think the latter is more useful and also the more logical extension.

My reasoning is that the PEP supports things like f(*a, *b) and it would be fairly logical to interpret f(*x for x in xs) as doing the *x thing for each x in the list xs.

最后,如 the Abstract section of the corresponding PEP 中所述,此功能并未完全排除:

This PEP does not include unpacking operators inside list, set and dictionary comprehensions although this has not been ruled out for future proposals.

所以,我们可能很快就会看到它(但肯定不是 3.6 :-),我希望我们能看到,它们看起来不错。