Python列表推导教程(List Comprehensions)
本文我们学习Python列表推导(list comprehensions)、集合推导(set comprehensions)以及字典推导(dictionary comprehensions)。
1.什么是列表推导
列表推导是基于迭代创建列表的简单方式。在创建过程中,迭代中的元素可以有条件被包括在新创建的列表中并根据需要进行转换。
迭代是可以被循环的数据结构。列表推导包括下面三个组件:
- 输出表达式
- 迭代数据
- 表示迭代数据成员的迭代变量
下面示例扎实如何生成一个列表元素平方的列表:
numbers = [1, 2, 3, 4, 5]
squares = [number**2 for number in numbers]
print(squares)
输出结果为:
[1, 4, 9, 16, 25]
也可以增加在迭代数据上增加条件创建更高级的列表推导。
下面示例增加条件大于2:
numbers = [1, 2, 3, 4, 5]
squares = [number**2 for number in numbers if number > 2]
print(squares)
输出:
[9, 16, 25]
2.列表推导 vs 循环
列表推导在计算和代码空间和时间两个方面都比for循环更有效,通常仅需一行代码实现。
下面代码通过for循环实现,代码比较繁琐,显然列表推导更容易写。
numbers = [1, 2, 3, 4, 5]
squares = []
for number in numbers:
if number > 2:
squares.append(number**2)
print(squares)
需要注意的是,每个列表推导可以使用for循环实现,反之则不一定。
那两者的执行速度如何呢?我们使用timeit库比较两者的执行效率。可以通过设定number参数表示执行次数,我们设定1000,000。
import timeit
def squares(size):
result = []
for number in range(size):
result.append(number*number)
return result
def squares_comprehension(size):
return [number*number for number in range(size)]
print(timeit.timeit("squares(50)", "from __main__ import squares", number = 1_000_000))
print(timeit.timeit("squares_comprehension(50)", "from __main__ import squares_comprehension", number = 1_000_000))
输出结果为:
10.609948187999999
7.189505247999998
这是我机器的运行结果,可能与你的执行结果不同。但显然列表推导比for循环效率要高。
3.列表推导 VS map 和 filter
列表推导式从函数式编程语言Haskell借鉴过来的简化表达。可以理解为filter和map函数的语法糖。从简洁和效率角度看,列表推导是for循环的最佳替代。
Lambda表达式
Lambda表达式是小的匿名函数。可以有任意个参数,但仅有一个表达式。通常情况下,Lambda表达式作为参数传递给函数,可以视为函数对象作为函数参数,如map和fliter函数。
Map函数
Map函数返回基于迭代的每个元素上应用一个函数并生成结果的迭代。下面与列表推导进行比较。
numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x**2, numbers))
print(squares)
# List Comprehension
numbers = [1, 2, 3, 4, 5]
squares = [number**2 for number in numbers]
print(squares)
输出:
[1, 4, 9, 16, 25]
[1, 4, 9, 16, 25]
Filter 函数
Filter 函数从迭代的每个中上应用一个函数并返回true的结果中创建新的迭代。与列表推导进行比较:
# Filter
numbers = [1, 2, 3, 4, 5]
filtered = list(filter(lambda x: x % 2 == 0, numbers))
print(filtered)
# List Comprehension
numbers = [1, 2, 3, 4, 5]
filtered = [number for number in numbers if number % 2 == 0]
print(filtered)
[2, 4]
[2, 4]
4.更复杂的列表推导
另外,创建列表推导式可以在迭代上应用多个条件表达式。
请看示例:
numbers = [1, 2, 3, 4, 5, 6, 18, 20]
squares = [number for number in numbers if number % 2 == 0 if number % 3 == 0]
print(squares)
输出结果:
[6, 18]
我们还可以在输出表达式上使用if-else子句。
请看示例:
numbers = [1, 2, 3, 4, 5, 6, 18, 20]
squares = ["small" if number < 10 else "big" for number in numbers if number % 2 == 0 if number % 3 == 0]
print(squares)
输出结果:
['small', 'big']
可读性
有时列表推导可能很复杂且非常难读。Python允许括号中的语句换行,我们可以利用该特性增强代码的可读性。举例如下:
numbers = [1, 2, 3, 4, 5, 6, 18, 20]
squares = [
"small" if number < 10 else "big"
for number in numbers
if number % 2 == 0
if number % 3 == 0]
print(squares)
输出结果:
['small', 'big']
然而从代码可读性角度考虑,有时使用for循环代替复杂的列表推导不失为更好的选择。
5.嵌套
嵌套循环
一些需要使用嵌套for循环实现的复杂任务,也可以使用列表推导实现同样任务。
假设有一个矩阵需要转成列表,使用两个for循环实现如下:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = []
for row in matrix:
for item in row:
flattened.append(item)
print(flattened)
输出如下:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
使用列表推导实现,需要注意的是for子句的顺序需与循环保持一致:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [item for row in matrix for item in row]
print(flattened)
结果一样,item是返回表达式,后面两个for为嵌套关系。
嵌套列表推导
另一方面,我们需要创建一个矩阵。我们可以使用嵌套列表推导,听起来比较疯狂,但概念比较简单。
列表推导返回list,是吧,所以你把列表推导作为另一个列表推导的输出表达式,则获得一个矩阵:
matrix = [[item for item in range(5)] for row in range(3)]
print(matrix)
输出为:
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]
range类型表示不可变的数值序列,主要作为循环次数。
6. 其他类型推导
在Python中,还有字典推导和集合推导。所有原则都一样,这里仅介绍一些细节差异。
字典推导
创建字典推导需要使用{},而不是[],另外输出表达式需要使用key:value。示例:
prices = {"beer": 2, "fish": 5, "apple": 1}
float_prices = {key:float(value) for key, value in prices.items()}
print(float_prices)
输出:
{'beer': 2.0, 'fish': 5.0, 'apple': 1.0}
结合推导
创建集合推导仅需要改变[]为{},请看示例:
numbers = [10, 10, 20, 30, 12, -20, 0, 1]
unique_squares = {number**2 for number in numbers}
print(unique_squares)
输出自动去重:
{0, 1, 100, 144, 400, 900}
7.生成器表达式
这里再说下Python中生成器表达式。和列表推导类似,区别是使用圆括号(),不在内存中存储列表。使用懒计算技术。列表推导一般不用于生成无限流或很大的数据集。生成器表达式比较适合这些场景。
8.总结
列表推导提供基于迭代创建列表的简单方式,相比for循环更好理解可读。可以在推导中使用条件语句,建议使用列表推导代替map和filter函数。还使用嵌套列表推导,字典列表推导,集合列表推导。生成器表达式更适合于大数据集和无限数据流。