Python自学手册
不知为何,你得到了嵌套在列表中的列表,可能像这样:
>>> groups=[["Hong","Ryan"],["Anthony","Wilhelmina"],["Margaret","Adrian"]]
|
但你想要的只是一个列表(没有嵌套),就像这样:
>>> expected_output=["Hong","Ryan","Anthony","Wilhelmina","Margaret","Adrian"]
|
你需要展平你的列表列表。
我们正在寻找一种“浅层”展平
我们可以将其视为一种浅层展平操作,即只将列表展平一层。 一种深层展平操作会处理列表的列表的列表的列表(以此类推),但这超出了我们当前用例的需求。
我们想出的展平策略应该也能作用于列表的列表,以及任何其他类型的可迭代对象可迭代对象的: 例如,元组列表应可展平:
>>> groups=[("Hong","Ryan"),("Anthony","Wilhelmina"),("Margaret","Adrian")]
|
甚至像 a 这样奇怪的类型dict_items对象(我们通过向字典请求其项来获取)也应该可以展平:
>>> fruit_counts={"apple":3,"lime":2,"watermelon":1,"mandarin":4} >>> fruit_counts.items() dict_items([('apple', 3), ('lime', 2), ('watermelon', 1), ('mandarin', 4)]) >>> flattened_counts=['apple',3,'lime',2,'watermelon',1,'mandarin',4]
|
用 a 展平可迭代对象的可迭代对象for循环
将可迭代对象的可迭代对象展平的一种方法是使用for循环。 我们可以进行一层循环来获取每个内部的可迭代对象。
然后我们进行第二层循环,从每个内部可迭代对象中获取每个物品。
forgroupingroups: fornameingroup: ...
|
然后将每个物品添加到一个新的列表中:
names=[] forgroupingroups: fornameingroup: names.append(name)
|
还有一个列表方法能让这更简洁一些,这个extend方法:
names=[] forgroupingroups: names.extend(group)
|
列表extend方法接受一个可迭代对象,并将你提供的可迭代对象中的每个物品追加到其中。
或者我们可以使用+=用于将每个列表连接到我们新列表的运算符:
names=[] forgroupingroups: names+=group
|
你可以将其视为对列表调用+=方法。 对于列表而言,这两种操作(extend和+=)是等效的。extend使用推导式展平可迭代对象的嵌套序列
Flattening iterables-of-iterables with a comprehension
这个嵌套的for循环带有一个append调用,看起来可能很熟悉:
names=[] forgroupingroups: fornameingroup: names.append(name)
|
这段代码的结构看起来像是我们可以复制粘贴到列表推导式中的东西.
在我们的方括号内,我们会先复制要追加的内容,然后是第一个循环的逻辑,接着是第二个循环的逻辑:
names=[ name forgroupingroups fornameingroup ]
|
这种推导式嵌套了两层,就像我们之前的嵌套循环一样。 注意,推导式中for子句的for顺序必须与for循环的顺序保持一致.
(有时令人困惑的)这些子句的顺序for部分原因是我建议将其复制粘贴到推导式中。 将一个for循环转换为推导式时,for和if子句保持相对位置不变,但你要添加的内容会从末尾移到开头。
我们能用它在推导式中展平吗?*在推导式中展平吗?
但是 Python 的*运算符呢? 我曾写过关于 Python 中前置星号符号的多种用途.
我们可以使用*在 Python 的列表字面量语法中([…]) 将可迭代对象解包为新列表:
>>> numbers=[3,4,7] >>> more_numbers=[2,1,*numbers,11,18] >>> more_numbers [2, 1, 3, 4, 7, 11, 18]
|
我们能用那个*运算符在推导式中解包一个可迭代对象吗?
names=[ *group forgroupingroups ]
|
我们不能。 如果我们尝试这样做,Python 会明确告诉我们那个*运算符不能在推导式中这样使用:
>>> names=[ ... *group ... forgroupingroups ... ] File "<stdin>", line 2 ] ^ SyntaxError: iterable unpacking cannot be used in comprehension
|
这一特性被特意排除在PEP 448,即那份将这种*-in-list-literal 语法添加到 Python 中的 Python 增强提案之外,原因是出于可读性的考量。
难道我们不能使用吗sum?
这是我见过的另一种列表展平技巧:
这起作用:
>>> names ['Hong', 'Ryan', 'Anthony', 'Wilhelmina', 'Margaret', 'Adrian']
|
但我发现这种技巧相当不直观。
我们在 Python 中使用+运算符来同时进行数字相加和序列拼接,而sum函数恰好适用于任何支持+运算符的对象(得益于鸭子类型但在我的脑海中,“sum”这个词暗示了算术:求和是将数字相加在一起.
我发现“合并”列表让人困惑,所以我不推荐这种方法.
简短插话:该算法sum使用的列表展平操作也使得速度变得非常慢 (翻译为中文: )。 在大O表示法中(对于时间复杂度爱好者而言),sum使用列表是O(n**2)而不是O(n).
那如果是itertools.chain?
还有一个常用于展平的工具:位于chain模块中的itertools实用程序。
chain接受任意数量的参数并返回一个迭代器:
>>> fromitertoolsimportchain >>> chain(*groups) <itertools.chain object at 0x7fc1b2d65bb0>
|
我们可以遍历那个迭代器,或者将其转换为另一种可迭代对象,比如列表:
>>> list(chain(*groups)) ['Hong', 'Ryan', 'Anthony', 'Wilhelmina', 'Margaret', 'Adrian']
|
实际上在chain上有一个专门用于展平单个可迭代对象的方法:
>>> list(chain.from_iterable(groups)) ['Hong', 'Ryan', 'Anthony', 'Wilhelmina', 'Margaret', 'Adrian']
|
使用chain.from_iterable比使用chain通过*更高效,因为*会在调用时立即解包整个可迭代对象。chain is called.
回顾:比较列表展平技术
如果你想惰性地展平一个可迭代对象的可迭代对象,我会使用itertools.chain.from_iterable:
>>> fromitertoolsimportchain >>> flattened=chain.from_iterable(groups)
|
这将返回一个迭代器,这意味着在遍历返回的可迭代对象之前不会执行任何操作:
>>> list(flattened) ['Hong', 'Ryan', 'Anthony', 'Wilhelmina', 'Margaret', 'Adrian']
|
并且在遍历过程中它会被消耗,因此两次遍历将导致可迭代对象为空:
如果你觉得 itertools.chain 用起来有点太晦涩难懂,那你或许会更倾向于用一个 for 循环 —— 在新创建的列表上调用 extend 方法,来逐个扩展(整合)每个可迭代对象中的元素:
names=[] forgroupingroups: names.extend(group)
|
或者一个for使用+=运算符的循环,作用于我们的新列表:
names=[] forgroupingroups: names+=group
|
不像chain.from_iterable,这两个for循环会构建新的列表,而不是惰性迭代器对象。
如果你觉得列表推导式可读性高(我喜欢用它来暗示“看,我们正在构建一个列表”),那么你可能更倾向于使用推导式:
names=[ name forgroupingroups fornameingroup ]
|
而如果你确实想要实现惰性求值(也就是迭代器的特性),但又不想用 itertools.chain 的话,你可以写一个生成器表达式,实现和 itertools.chain.from_iterable 完全一样的功能:
names=( name forgroupingroups fornameingroup )
|
有什么疑问和需要的,欢迎评论区留言