列表推导式可以使用非常简洁的方式对列表或其他可迭代对象的元素进行遍历和过滤,快速生成满足特定需求的列表,代码具有非常强的可读性,是Python程序开发时应用最多的技术之一。Python的内部实现对列表推导式做了大量优化,可以保证很快的运行速度,也是推荐使用的一种技术。列表推导式的语法形式为:
[表达式 for 变量 in 序列或迭代对象 if 条件表达式]
列表推导式在逻辑上等价于一个循环语句,只是形式上更加简洁。例如,
>>> aList = [x*x for x in range(10)]
相当于
>>> aList = []
>>> for x in range(10):
aList.append(x*x)
大家应该知道一个故事,说是阿凡提(也有的说是阿基米德,这不是重点)与国王比赛下棋,国王说要是自己输了的话阿凡提想要什么他都可以拿得出来。阿凡提说那就要点米吧,棋盘一共64个小格子,在第一个格子里放1粒米,第二个格子里放2粒米,第三个格子里放4粒米,第四个格子里放8粒米,以此类推,后面每个格子里的米都是前一个格子里的2倍,一直把64个格子都放满。那么到底需要多少粒米呢,其实使用列表推导式再结合内置函数sum()就很容易知道答案。
>>> sum([2**i for i in range(64)])
18446744073709551615
按一斤大米约26000粒计算,为放满棋盘,需要大概350亿吨大米。结果可想而知,最后国王没有办法拿出那么多米。
接下来再通过几个示例来进一步展示列表推导式的强大功能。
(1)实现嵌套列表的平铺
>>> vec = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
在这个列表推导式中有2个循环,其中第一个循环可以看做是外循环,执行的慢;而第二个循环可以看做是内循环,执行的快。上面代码的执行过程等价于下面的写法:
>>> vec = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> result = []
>>> for elem in vec:
for num in elem:
result.append(num)
>>> result
[1, 2, 3, 4, 5, 6, 7, 8, 9]
当然,这里演示的只是一层嵌套列表的平铺,如果有多级嵌套或者不同子列表嵌套深度不同的话,就不能使用上面的思路了。这时,可以使用函数递归实现。
(2)过滤不符合条件的元素
在列表推导式中可以使用if子句对列表中的元素进行筛选,只在结果列表中保留符合条件的元素。例如下面的代码可以列出当前文件夹下所有Python源文件:
>>> import os
>>> [filename for filename in os.listdir('.') if filename.endswith('.py')]
下面的代码用于从列表中选择符合条件的元素组成新的列表:
>>> aList = [-1, -4, 6, 7.5, -2.3, 9, -11]
>>> [i for i in aList if i>0] #所有大于0的数字
[6, 7.5, 9]
再例如,已知有一个包含一些同学成绩的字典,现在需要计算所有成绩的最高分、最低分、平均分,并查找所有最高分同学,代码可以这样编写:
>>> scores = {"Zhang San": 45,
"Li Si": 78,
"Wang Wu": 40,
"Zhou Liu": 96,
"Zhao Qi": 65,
"Sun Ba": 90,
"Zheng Jiu": 78,
"Wu Shi": 99,
"Dong Shiyi": 60}
>>> highest = max(scores.values()) #最高分
>>> lowest = min(scores.values()) #最低分
>>> average = sum(scores.values())/len(scores) #平均分
>>> highest, lowest, average
(99, 40, 72.33333333333333)
>>> highestPerson = [name for name, score in scores.items() if score == highest]
>>> highestPerson
['Wu Shi']
(3)同时遍历多个列表或可迭代对象
>>> [(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
对于包含多个循环的列表推导式,一定要清楚多个循环的执行顺序或“嵌套关系”。例如,上面的代码等价于
>>> result = []
>>> for x in [1, 2, 3]:
for y in [3, 1, 4]:
if x != y:
result.append((x,y))
>>> result
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
(4)使用列表推导式实现矩阵转置
>>> matrix = [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
对于嵌套了列表推导式的列表推导式,一定要清楚其执行顺序。例如,上面列表推导式的执行过程等价于下面的代码
>>> matrix = [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
>>> result = []
>>> for i in range(len(matrix[0])):
result.append([row[i] for row in matrix])
>>> result
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
如果把内层的列表推导式也展开的话,完整的执行过程可以通过下面的代码来模拟:
>>> matrix = [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
>>> result = []
>>> for i in range(len(matrix[0])):
temp = []
for row in matrix:
temp.append(row[i])
result.append(temp)
>>> result
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
(5)列表推导式中可以使用函数或复杂表达式
>>> def f(v):
if v%2 == 0:
v = v**2
else:
v = v+1
return v
>>> print([f(v) for v in [2, 3, 4, -1] if v>0])
[4, 4, 16]
(6)列表推导式支持文件对象迭代
>>> with open('C:\\RHDSetup.log', 'r') as fp:
print([line for line in fp])
(7)使用列表推导式生成100以内的所有素数
>>> from math import sqrt
>>> [ p for p in range(2, 100) if 0 not in [ p% d for d in range(2, int(sqrt(p))+1)] ]
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]