跳至主要内容

Python 列表推导式教程

学习如何在 Python 中高效使用列表推导式来创建列表,替代(嵌套)for 循环以及 map()、filter() 和 reduce() 等函数,等等!
· 14分钟 阅读

使用 AI 探索

在线运行和编辑此教程中的代码

运行代码

在进行数据科学工作时,您可能会需要读取列表的列表、按列名过滤、从列表中移除元音或将矩阵拍平。您当然可以使用 lambda 函数或 for 循环;正如您所知,做法有很多。另一种方法就是使用列表推导式。

本教程将围绕这个主题展开:

  • 首先,您将简要回顾什么是 Python 列表,以及它与其他 Python 数据结构的对比;
  • 接着,深入 Python 列表推导式:了解列表背后的数学思想,如何构造推导式,如何将其改写为 for 循环或 lambda 函数……您不仅会阅读相关内容,还会动手做一些练习!
  • 当掌握基础后,就该通过添加条件来微调推导式了:学习如何在推导式中加入条件,以及如何处理多个 if 条件和 if-else 语句。
  • 最后,您将学习嵌套列表推导式,以便对列表进行多重迭代。

如果您也想把推导式与迭代器、生成器一起系统学习?请查看 DataCamp 的 Python Data Science Toolbox 课程!

python list comprehension

Python 列表

到现在为止,您很可能已经用过多种数据类型的值。您将每个值分别保存在变量中:每个变量代表一个值。然而,在数据科学中,您经常需要处理大量数据点,这样将每个值都存入不同变量就会非常困难。相反,您可以把这些值都存入一个 Python 列表。

列表是 Python 中四种内置数据结构之一。您可能还知道的其他结构有元组、字典和集合。Python 列表不同于例如 int 或 bool,它是一种复合数据类型:您可以把多个值分组到列表中。事实上,这些值不必是相同类型:可以是布尔、字符串、整数等的组合。

需要强调的是,列表是有序的条目或对象的集合。这使得 Python 的列表属于“序列类型”,其行为类似于序列。这意味着它们可以被迭代;其他序列的例子有字符串、元组或集合。

提示:如果您想进一步了解、测试或练习 Python 列表的知识,可以在这里查看关于 Python 列表的常见问题。

回到实践层面:列表使用一对方括号构建;在括号内,使用逗号分隔各个值。然后您可以将列表赋给一个变量。放入 Python 列表的值可以是任何数据类型,甚至也可以是列表!

看看下面这个列表示例:

# Assign integer values to `a` and `b`
a = 4
b = 9

# Create a list with the variables `a` and `b` 
count_list = [1,2,3,a,5,6,7,8,b,10]

提示:在上面的 DataCamp Light 代码块内的 IPython shell 中构建您自己的列表!

Python 列表推导式

回顾了 Python 列表之后,您很容易看到在 Python 中定义和创建列表有时会很繁琐:逐个输入所有值会耗费不少时间,而且容易出错。

Python 中的列表推导式按如下方式构造:

list_variable = [x for x in iterable]

但如何得到这种“公式化”的构建与使用方式呢?让我们更深入一点。

Python 列表推导式:背后的数学

幸运的是,Python 为您提供了解决方案:它允许您用一种数学记号来实现这一点:列表推导式。

回忆在数学中,描述列表(或集合、元组、向量)的常见方式是:

S = {x² : x in {0 ... 9}}
V = (1, 2, 4, 8, ..., 2¹²)
M = {x | x in S and x even}

换句话说,上述定义实际上表达了以下含义:

  • 序列 S 包含 0 到 9(含)之间的值,每个都被平方。
  • 序列 V 则包含底数为 2 的幂。序列中第一个元素的幂是 0,第二个是 1,以此类推,直到 12。
  • 最后,序列 M 取自序列 S 的元素,但只包含其中的偶数。

如果这些定义让您有点头疼,看看这些定义将产生的实际列表:

S = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
V = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096}
M = {0, 4, 16, 36, 64}

您可以清楚地看到每个列表的结果以及其中描述的运算!

既然理解了列表背后的一些数学思想,您就可以用列表推导式在 Python 中实现这种数学记号了!看看下面的代码行:

S = [x**2 for x in range(10)]
V = [2**i for i in range(13)]
M = [x for x in S if x % 2 == 0]

这看起来与您刚才看到的数学定义非常相似,对吧?

如果此刻还有点迷茫也没关系;即使您不是数学高手,只要花点时间学习,这些推导式也相当容易掌握。再仔细看看上面代码块中的 Python 代码。

您会看到,代码表达的是:

  • 列表 S 使用第一节提到的方括号构建。在括号内,有一个元素 x,被提升到 2 次幂。现在,您只需要知道要对多少个值(以及哪些值!)进行平方。这由 range(10) 决定。考虑到这些,您可以得出结论:将 0 到 9 的所有数字都平方。
  • 列表 V 包含底数为 2 的幂。和之前一样,现在您需要知道使用哪一个幂或 i。您看到此处的 i 来自 range(13),这意味着从 0 到 12。也就是说,列表将有 13 个值——分别是 2 的 0、1、2、…一直到 12 次幂。
  • 最后,列表 M 包含属于 S 的元素,但前提是它们能被 2 整除且没有余数。取模需要为 0。换句话说,列表 M 由存储在列表 S 中的偶数值组成。

现在把这些都写出来,是不是更清楚了?

回顾与练习

简而言之,您会发现这些代码行都有一些共同元素:

  • 方括号,它是 Python 列表的标志;
  • for 关键字,后接一个表示列表项的变量;以及
  • in 关键字,后接一个序列(也可以是列表!)。

这就得到您在本节开头看到的代码片段:

list_variable = [x for x in iterable]

现在轮到您来动手开始使用 Python 列表推导式了!让我们紧贴之前见过的数学列表:

Q = {$x^3$: x in {0 ... 10}}
# Define Q
Q = _________________________

列表推导式可以替代……

列表推导式可以完全替代 for 循环、lambda 函数以及 map()、filter() 和 reduce() 函数。更重要的是,对某些人来说,列表推导式甚至更容易理解和在实践中使用!下一节您将读到更多内容!

不过,如果您想进一步了解 Python 中的函数与 lambda 函数,请查看我们的 Python 函数教程。

For 循环

如您所知,for 循环用于将一段代码重复执行固定次数。列表推导式是 for 循环的很好替代,因为它更为紧凑。考虑以下示例,从变量 numbers 开始,它被定义为从 0 到 10(不含)的范围。

请记住,传递给 range() 函数的数字,实际上是您希望从 0 开始生成的整数个数。这意味着 range(10) 将返回 [0,1,2,3,4,5,6,7,8,9]。

# Initialize `numbers`
numbers = range(10)

如果现在想对 numbers 中的每个元素执行某种操作,您可以像这样使用 for 循环:

# Initialize `new_list`
new_list = []

# Add values to `new_list`
for n in numbers:
    if n%2==0:
        new_list.append(n**2)

# Print `new_list`
print(new_list)
[0, 4, 16, 36, 64]

这很好,但请看下面的列表推导式示例,它用更紧凑的记法完成了同样的事情:

# Create `new_list` 
new_list = [n**2 for n in numbers if n%2==0]

# Print `new_list`
print(new_list)
[0, 4, 16, 36, 64]

让我们用一个小测试来比较推导式与 for 循环的性能差异:您可以用 timeit 库非常方便地对小段 Python 代码计时。在这里,要测试的小段代码是 for 循环(为了方便,将其放入名为 power_two() 的函数中)与上面写出的等价列表推导式。

注意您还可以传入要执行的次数。本例中在 number 参数中设为 10000。

# Import `timeit`
import timeit
# Print the execution time
print(timeit.timeit('[n**2 for n in range(10) if n%2==0]', number=10000))
0.05234622399802902
# Define `power_two()` 
def power_two(numbers):
    for n in numbers:
        if n%2==0:
            new_list.append(n**2)
    return new_list

# Print the execution time 
print(timeit.timeit('power_two(numbers)', globals=globals(), number=10000))
0.07795589299712447

注意在这段代码中,您还添加了 globals 参数,从而使代码在当前的全局命名空间内执行。如果您有用户自定义函数(UDF),如上例中的 power_two(),这非常方便。或者,您也可以传入包含 import 语句的 setup 参数。详情可在此处阅读。

提示:查看 DataCamp 的 Python 中的循环 教程,了解更多关于循环的信息。

结合 map()、filter() 和 reduce() 的 Lambda 函数

Lambda 函数也被称为“匿名函数”或“没有名称的函数”。这意味着您只在创建它们时使用这种函数。Lambda 函数的名字来源于 Python 中的 lambda 关键字,它用于声明这类函数,而不是常规的 def 关键字。

您通常将这些函数与 map()、filter() 和 reduce() 函数一起使用。

如何用推导式替代与 Lambda 组合的 map()

您可以像下面的示例一样改写 map() 与 lambda 的组合:

# Initialize the `kilometer` list 
kilometer = [39.2, 36.5, 37.3, 37.8]

# Construct `feet` with `map()`
feet = map(lambda x: float(3280.8399)*x, kilometer)

# Print `feet` as a list 
print(list(feet))
[128608.92408000001, 119750.65635, 122375.32826999998, 124015.74822]

现在,您可以轻松用列表推导式替换定义变量 feet 的这组函数,步骤参考上一节提到的要素:

  • 以方括号开始。
  • 在方括号中加入 lambda 函数的主体:float(3280.8399)*x。
  • 接着添加 for 关键字,并确保重复序列元素 x,因为您已经在函数主体中引用了它。
  • 别忘了指定 x 的来源:添加 in 关键字,后接要从中获取 x 的序列。本例中,您将转换 kilometer 列表的元素。

按上述步骤,您会得到如下结果:

# Convert `kilometer` to `feet` 
feet = [float(3280.8399)*x for x in kilometer]

# Print `feet`
print(feet)
[128608.92408000001, 119750.65635, 122375.32826999998, 124015.74822]

filter() 与 Lambda 到列表推导式

既然已经看到如何将 map() 与 lambda 的组合轻松转换,那么也可以处理包含 Python filter() 与 lambda 的代码并改写之。

考虑下面的示例:

# Map the values of `feet` to integers 
feet = list(map(int, feet))

# Filter `feet` to only include uneven distances 
uneven = filter(lambda x: x%2, feet)

# Check the type of `uneven`
type(uneven)

# Print `uneven` as a list
print(list(uneven))
[122375, 124015]

要改写上述代码行,实际上可以使用两个列表推导式,分别存入 feet 和 uneven 变量中。

首先,改写用于将 feet 列表元素转换为整数的 map()。然后处理 filter():取出 lambda 函数的主体,使用 for 和 in 关键字将 x 与 feet 逻辑连接:

# Constructing `feet` 
feet = [int(x) for x in feet]

# Print `feet`
print(feet)

# Get all uneven distances
uneven = [x%2 for x in feet]

# Print `uneven`
print(uneven)
[128608, 119750, 122375, 124015]
[0, 0, 1, 1]

用推导式替代 reduce() 与 Lambda

最后,您也可以把与 reduce() 一起使用的 lambda 改写为更紧凑的代码。看看下面的示例:

# Import `reduce` from `functools` 
from functools import reduce

# Reduce `feet` to `reduced_feet`
reduced_feet = reduce(lambda x,y: x+y, feet)

# Print `reduced_feet`
print(reduced_feet)
[128608, 119750, 122375, 124015]
494748

注意在 Python 3 中,reduce() 被移至 functools 包。因此,像上例一样,使用前需要导入该模块。

上面的代码块相当冗长,是吧?

让我们来改写它!

请注意!您需要考虑到无法使用 y。列表推导式只能处理一个元素,比如本教程示例中一直出现的 x。

该如何解决?

在这种情况下,sum() 之类的聚合函数就派上用场了:

# Construct `reduced_feet`
reduced_feet = sum([x for x in feet])

# Print `reduced_feet`
print(reduced_feet)
494748

注意想一想,将 reduce() 与 lambda 改写为聚合函数是合理的:这与您在 SQL 中使用聚合函数以减少查询返回记录数非常相似。本例中,使用 sum() 将 feet 中的元素聚合为一个最终值!

请注意,尽管这种方法在 SQL 中的性能可能不佳,但在 Python 中这绝对是推荐做法!

带条件的列表推导式

既然已经理解了列表推导式的基础,现在是时候借助条件来调整推导式的控制流了。

# Define `uneven`
uneven = [x/2 for x in feet if x%2==0]

# Print `uneven` 
print(uneven)
[64304.0, 59875.0]

注意您可以很容易地用 Python for 循环改写上面的代码块!

# Initialize and empty list `uneven` 
uneven = []

# Add values to `uneven` 
for x in feet:
    if x % 2 == 0:
        x = x / 2
        uneven.append(x)

# Print `uneven` 
print(uneven)
[64304.0, 59875.0]

多个 If 条件

现在您已经理解了如何添加条件,接下来将下面的 for 循环转换成带条件的列表推导式。

divided = []

for x in range(100):
    if x%2 == 0 :
        if x%6 == 0:
            divided.append(x)

请小心,您会看到下面的 for 循环包含两个条件!仔细思考如何解决。

divided = [x for x in range(100) if x % 2 == 0 if x % 6 == 0]

print(divided)
[0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]

If-Else 条件

当然,使用多于一个条件更为常见。没错,您更常看到 if 与 elif、else 的组合。若计划重写代码,该怎么处理呢?

看看下面这个在 for 循环中包含更复杂条件的示例:

[x+1 if x >= 120000 else x+5 for x in feet]
[128609, 119755, 122376, 124016]

再看下面这个代码块,它是对上述代码的改写:

for x in feet:  
    if x >= 120000:
        x + 1
    else: 
        x+5

您会发现这基本是同一段代码,但结构不同:最后的 for x in feet 现在初始化了 for 循环。之后,添加条件 if x >= 120000 以及当条件为 True 时要执行的代码行:x + 1。如果条件为 False,则执行推导式中最后那部分代码:x+5。

嵌套列表推导式

除了条件,您还可以通过在推导式中再嵌套推导式来调整逻辑。当您需要处理列表的列表时,这非常方便:例如生成列表的列表、转置列表的列表,或将其拍平成普通列表,用嵌套推导式都变得非常简单。

请看以下示例:

list_of_list = [[1,2,3],[4,5,6],[7,8]]

# Flatten `list_of_list`
[y for x in list_of_list for y in x]
[1, 2, 3, 4, 5, 6, 7, 8]

您将一个相对简单的列表的列表赋给变量 list_of_list。下一行执行一个返回普通列表的推导式。其本质是:取出 list_of_list 中嵌套列表(x)的列表元素(y),并返回由这些包含在 x 中的元素 y 组成的列表。

您会看到,嵌套推导式示例中使用的大多数关键字和元素与简单推导式示例相似:

  • 方括号
  • 两个 for 关键字,分别后接一个表示列表的列表的条目(x)和一个表示嵌套列表条目的变量(y);以及
  • 两个 in 关键字,分别后接列表的列表(list_of_list)和一个列表条目(x)。

大多数组件只是使用了两次,并且您再向上一层(或更深一层,取决于角度!)。

需要一些时间适应,但其实相当简单,对吧?

现在再看另一个例子,您会看到也可以使用两组方括号来改变嵌套推导式的逻辑:

matrix = [[1,2,3],[4,5,6],[7,8,9]]

[[row[i] for row in matrix] for i in range(3)]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

现在练习一下:将上面的代码块改写为嵌套 for 循环。如果需要解题思路,请回看本教程前面的相关部分。

transposed = []

for i in range(3):
     transposed_row = []
     for row in matrix:
            transposed_row.append(row[i])
     transposed.append(transposed_row)

在需要创建一个实际上是矩阵的列表的列表时,您也可以使用嵌套推导式。看看下面的例子:

matrix = [[0 for col in range(4)] for row in range(3)]

matrix
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

提示:练习一下您的循环技巧,把上面的代码块改写为嵌套 for 循环!

解答如下。

for x in range(3):
    nested = []
    matrix.append(nested)
    for row in range(4):
        nested.append(0)

如果您想做点额外练习,把这个 for 循环改写为 while 循环。解答如下:

x = 0
matrix =[]

while x < 3:
    nested = []
    y = 0
    matrix.append(nested)
    x = x+1
    while y < 4:
        nested.append(0)
        y= y+1

最后,需要知道的一点是,您还可以使用 int() 等函数将 feet 列表中的条目转换为整数。通过在另一个推导式中包裹 [int(x) for x in feet],您可以很容易地把列表构造成矩阵或列表的列表:

[[int(x) for x in feet] for x in feet]
[[128608, 119750, 122375, 124015],
 [128608, 119750, 122375, 124015],
 [128608, 119750, 122375, 124015],
 [128608, 119750, 122375, 124015]]

精通用于数据科学的 Python

恭喜!您已经完成本教程,学习了列表推导式——这是 Python 在数据科学中经常使用的一种机制。现在您理解了它的工作方式,也就准备好进一步学习字典、集合等推导式了!

别忘了,您可以通过 DataCamp 的每日练习模式每天打磨 Python 技能!在您的仪表板上就能找到。如果您还不了解每日练习模式,请到这里阅读!并在这里探索 DataCamp 全部 Python 课程目录。

尽管列表推导式可以让代码更简洁,但务必要确保最终代码尽可能易读,因此应避免过长的单行代码,以保证代码对用户友好。

Python 列表推导式常见问题

什么是 Python 中的列表推导式?

这是一种简洁语法,用于从一个范围或可迭代对象创建列表,并对其每个元素应用指定操作。与 for 循环、lambda 函数、条件语句等替代方案相比,它的执行速度通常更快。

何时使用列表推导式?

当我们需要从一个 range 对象或可迭代对象(另一个列表、元组、集合等)创建 Python 列表,并对输入对象的每个条目应用某个操作时使用。它在所求表达式相对简单时效果最佳。两个典型用例是:对输入对象进行过滤,以及将多维可迭代对象(如列表的列表)拍平。

列表推导式可以作用于哪些序列?

一个 range 对象或任意可迭代对象,例如字符串、另一个列表、列表的列表、元组、集合、字典等。对于嵌套推导式,数据集合可以是不同种类的混合。

列表推导式语法的主要元素有哪些?

包围推导式的方括号;指代输入序列每个条目的变量;待计算的表达式;应用该表达式的数据集合(或集合);必需的关键字 for 和 in;在需要时使用的 if、else、not 关键字;数学与比较运算符。

列表推导式可替代哪些 Python 结构?

列表推导式是 for 循环(包括嵌套循环)、lambda 函数、Python 内置函数 map()、filter() 与 reduce(),以及条件语句的更简洁替代方案。

在 Python 中使用列表推导式有哪些好处?

性能快、语法紧凑、单行代码易读易调试、节省程序的垂直空间。

列表推导式的主要缺点是什么?

在某些情况下,列表推导式可能较难实现和阅读,例如表达式过于复杂或嵌套层次过多。

如何将列表的列表拍平?

通过使用嵌套列表推导式即可。例如,给定 list_of_lists = [[1, 2, 3, 4], [5, 6, 7], [8, 9]],可以用如下代码将其拍平:[item for lst in list_of_lists for item in lst]。

能否在列表推导式的表达式中进行赋值?

可以,从 Python 3.8 开始,尽管这种操作并不常见。您应使用海象运算符 :=。例如,下面的推导式会生成 5 次 1 到 10(含)之间的随机整数(需先导入 random),检查其是否大于 3;若是,则赋给变量 x,并将其加入生成的列表:[x for _ in range(5) if (x := random.randint(1, 10)) > 3]。

Python 中还存在哪些类型的推导式?

还有集合、字典和生成器推导式,它们的语法与列表推导式类似。Python 中没有元组推导式。

主题