Python 理解解析式,列表解析式是我最喜欢的Python特性之一。列表解析式乍看起来有点神秘,但在完全理解之后就会发现其结构实际上非常简单。
理解的关键在于,相比针对各种容器的for
循环,列表解析式相当于语法上更加简化紧凑的改进版。
这种东西有时称为语法糖,用来快速完成一些常见功能,从而减轻了Python程序员的负担。来看下面的列表解析式:
>>> squares = [x * x for x in range(10)]
这个解析式生成一个列表,包含从0到9的所有整数的平方:
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
如果想用纯for
循环构建相同的列表,可能会这么写:
>>> squares = []
>>> for x in range(10):
... squares.append(x * x)
这是一个非常简单的循环,对吧?如果回过头来对比for
循环版本和列表解析式版本,从中会发现一些共同点进而总结出一些模式。归纳其中的常见结构,最终会得到类似下面这样的模板:
values = [expression for item in collection]
上面的列表解析式“模板”等价于下面的for
循环:
values = []
for item in collection:
values.append(expression)
这里首先设置一个新的列表实例来接受输出值,然后遍历容器中的所有元素,用任意表达式处理每个元素,接着将各个结果添加到输出列表中。
这是一种固定模式,可以将许多for
循环转换为列表解析式,反之亦然。现在再为这个模板添加一个更有用的功能,即使用条件来过滤元素。
列表解析式可以根据某些条件过滤元素,将符合条件的值添加到输出列表中。来看一个例子:
>>> even_squares = [x * x for x in range(10)
if x % 2 == 0]
这个列表解析式将得到从0到9所有偶数整数的平方组成的列表。它使用取模(%
)运算符返回两数相除后的余数,在这个例子中用来测试一个数是否是偶数。这个解析式能得到预期的结果:
>>> even_squares
[0, 4, 16, 36, 64]
与第一个例子类似,这个新的列表解析式可以转化为一个等价的for
循环:
even_squares = []
for x in range(10):
if x % 2 == 0:
even_squares.append(x * x)
现在再次尝试从这个列表解析式和对应的for
循环中归纳出转换模式。这次需要向模板中添加一个过滤条件用来决定输出列表将要包含的值。下面是修改后的列表理解式模板:
values = [expression
for item in collection
if condition]
同样,这个列表解析式可转换为下面这种模式的for
循环:
values = []
for item in collection:
if condition:
values.append(expression)
这种转换也很简单,只是对前面的那个固定模式稍作改进。希望这种讲解方式能消除列表解析式的神秘感。列表解析式是个有用的工具,所有Python程序员都应该掌握。
在继续之前,需要指出的是Python不仅支持列表解析式,对于集合和字典也有类似的语法糖。
下面是集合解析式:
>>> { x * x for x in range(-9, 10) }
set([64, 1, 36, 0, 49, 9, 16, 81, 25, 4])
列表会保留元素的顺序,但Python集合是无序类型。所以在将元素加到set
容器时顺序是随机的。
下面是字典解析式:
>>> { x: x * x for x in range(5) }
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
这两种解析式在实践中都很有用。不过Python解析式中有一个需要注意的地方:在熟悉了解析式之后,很容易就会编写出难以阅读的代码。如果不小心的话,可能就要面对许多难以理解的列表、设置和字典解析式。好东西太多了通常会适得其反。
在经历了许多烦恼之后,我给解析式设定的限制是只能嵌套一层。在大多数情况下,多层嵌套最好直接使用for
循环,这样代码更加易读且容易维护。
关键要点
-
解析式是Python中的一个关键特性。理解和应用解析式会让代码变得更具Python特色。
-
解析式只是简单
for
循环模式的花哨语法糖。在理解其中的模式之后,就能对解析式有直观的理解。 -
除了列表解析式之外,还有集合解析式和字典解析式。