转载声明:本文翻译转载自 mathspp.com 的文章 Unpacking inside comprehensions,作者 Rodrigo Girão Serrão,原文发布于 2026 年 10 月 15 日。本文为翻译转载,版权归原作者所有。
从 Python 3.15 开始,你可以在列表推导式内部用星号 * 进行解包:
1 | nested = [[0, 1, 2], [3, 4], [], [5, 6, 7]] |
这个新的解包特性同样适用于集合推导式、字典推导式和生成器表达式。本文将讲解这套语法、Python 3.14 及更早版本中的替代写法,并展示一些真实世界的应用示例。
「Python 3.15 的 15 天」系列
为了庆祝 Python 3.15 发布,作者将在接下来的 15 个工作日里,每天讲解一个 Python 3.15 的新特性——解释清晰、附带示例,让你不必自己去翻变更日志。
在推导式内部解包
如果你有一个嵌套的列表的列表,可以通过在推导式内部解包把它拍平:
1 | nested = [[0, 1, 2], [3, 4], [], [5, 6, 7]] |
在 3.15 引入解包语法之前,你得写一个嵌套的列表推导式:
1 | nested = [[0, 1, 2], [3, 4], [], [5, 6, 7]] |
嵌套列表推导式的结构,对应着等效的嵌套 for 循环的结构:
1 | nested = [[0, 1, 2], [3, 4], [], [5, 6, 7]] |
如果你熟悉列表方法,就会知道去掉内层循环、改用 extend 方法,可以把嵌套循环的版本写得更地道:
1 | nested = [[0, 1, 2], [3, 4], [], [5, 6, 7]] |
调用 extend 可以把 sublist 的所有元素加进拍平后的列表,而无需显式迭代——这正和在推导式内部解包如出一辙。事实上,「在 for 循环里调用 extend」正是你应该改用「在列表推导式内部解包」的信号。
在集合推导式内部解包
在集合推导式内部解包的方式完全相同,只是最终的集合类型是集合(set)而非列表:
1 | nested = [[0, 1, 2], [3, 4], [], [5, 6, 7]] |
上面的代码与前面的例子一致,但创建的是一个扁平的集合而不是列表。
再举一个例子,下面的代码利用集合的 update 方法,从一个字符串列表中收集所有去重后的字符:
1 | words = ["hello", "world"] |
在 for 循环里用 set.update,类似于在 for 循环里用 list.extend——它也是一个信号,说明你可能想改用「在集合推导式内部解包」:
1 | words = ["hello", "world"] |
在字典推导式内部解包
字典同样可以在字典推导式内部解包,但你需要用两个星号 ** 而不是一个 *:
1 | configs = [ |
在字典推导式内部解包字典,优于在 for 循环里反复调用 dict.update:
1 | configs = [ |
代码示例
本节将展示几个标准库中的例子,它们都可以改用「在推导式内部解包」。
shutil 中忽略名单的构建
shutil 模块中有一个函数,从一系列模式(pattern)生成一个忽略名单列表:
1 | # shutil.py |
这段代码正是「在 for 循环里调用 list.extend」的典型模式。也就是说,它可以改写为:
1 | return { |
dataclasses 中 slots 的计算
dataclasses 模块中有一段代码,用于计算 dataclass 将会继承的 slots:
1 | inherited_slots = set( |
这里用 itertools.chain.from_iterable 来拍平 map(_get_slots, cls.__mro__[1:-1]) 产出的「slots 的可迭代对象」,然后再包一层 set,构建出所有被继承 slots 的集合。
为了帮助理解上面的代码,下面演示如何用 chain.from_iterable 拍平一个嵌套的可迭代对象:
1 | from itertools import chain |
使用 chain.from_iterable 的优势在于它是惰性的——如果你需要迭代嵌套可迭代对象的元素,chain.from_iterable 是最佳选择。然而,在像 dataclasses 这样的场景里,对 chain.from_iterable 的调用只是个中间步骤,因为你拿到链之后马上就转成了集合:
1 | inherited_slots = set( |
chain.from_iterable 在这里只是个辅助手段,用于得到一个包含 cls 方法解析顺序(__mro__)中所有类的全部 slots 的集合。既然这里根本没利用到 chain.from_iterable 的惰性,就可以换成「在集合推导式内部解包」:
1 | inherited_slots = {*_get_slots(c) for c in cls.__mro__[1:-1]} |
改写后的代码更优雅,因为它更直接。
在生成器表达式内部解包
生成器表达式同样支持在内部解包。一般来说,生成器表达式
1 | g = (*sub for sub in iterable) |
等效于一个带双层 for 循环的生成器函数:
1 | def generator(iterable): |
再举一例:在 Python 3.14 及更早版本中,chain.from_iterable 是惰性拍平可迭代对象最方便的方式。现在,chain.from_iterable 可以被替换为带解包的生成器表达式:
1 | # Before: |
上面的代码片段基于 dataclasses 的那段代码给出了具体示例,只是去掉了最后构建集合这一步。一般而言,调用
1 | chain.from_iterable(iterable) |
可以变成
1 | (*sub for sub in iterable) |
当可迭代对象是一个更复杂的表达式时——比如前例中的 map(...)——使用生成器表达式尤其有用。
Python 3.15 的精彩远不止于此
从两个新内置的 frozendict 和 sentinel,到惰性导入(lazy imports)、包启动配置文件、typing 改进,以及更多色彩支持,Python 3.15 还有很多值得一看的东西。如果你想进一步了解 Python 3.15 的新特性,可以看看作者正在连载的「Python 3.15 的 15 天」系列——15 天里每天讲解一个 Python 3.15 新特性。
参考资料
- PEP 798 – Unpacking in Comprehensions(推导式中的解包),https://peps.python.org/pep-0798/ [原文访问日期:2026-10-15];