编写高质量Python (第28条) 控制推导逻辑的字表达式不要超过两个
第 28 条 控制推导逻辑的字表达式不要超过两个
除了最基本的用法(参见 第 27 条)外,列表推导还支持多层循环。例如,要把矩阵转化为普通的一维列表,那么可以在推导时,使用两条 for 字表达式。这些子表达式会按照从左到右的顺序解读。
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for row in matrix for x in row]
print(flat)
>>>
[1, 2, 3, 4, 5, 6, 7, 8, 9]
这样写简单易懂,这也正是多层循环在列表推导之中的合理用法。多层循环还可以用来重制那种两层深的结构。例如,如果要根据二维矩阵中每个元素的平方值构建一个新的二维矩阵,那么可以采用下面这种写法。这看起来有点儿复杂,因为它把小的推导逻辑 [x ** 2 for x in row] 嵌到了大的推导逻辑里面,不过,这行语句总体上不难理解。
squared = [[x ** 2 for x in row] for row in matrix]
print(squared)
>>>
[[1, 4, 9], [16, 25, 36], [49, 64, 81]]
如果推导过程中还要再加一层循环,那么语句就会变得很长,必须把它分成多行来写,例如下面是把一个三维矩阵转化为普通一维列表的代码
my_lists = [
[[1, 2, 3], [4, 5, 6]]
]
flat = [x for sublist1 in my_lists
for sublist2 in sublist1
for x in sublist2]
在这种情况下,采用列表推导来实现,其实并不会比传统的 for 循环节省多少代码。下面常见的 for 循环改写刚才的例子。这次我们通过级别不同的缩进表示每一层循环的深度,这要比刚才那种三层矩阵的列表推导更加清晰。
flat = []
for sublist1 in my_lists:
for sublist2 in sublist1:
flat.extend(sublist2)
推导的时候,可以使用多种 if 条件。如果这些 if 条件出现在同一层循环内,那么它们的关系默认是 and 关系,也就是必须同时成立。例如,如果要用原列表中大于 4 且是偶数的值来构建新列表,那么既可以连用两个 if,也可以只用一个 if,下面两种写法效果相同。
a = list(range(1, 11))
b = [x for x in a if x > 4 if x % 2 == 0]
c = [x for x in a if x > 4 and x % 2 == 0]
在推导时,每一层的 for 字表达式都可以带有 if 语句。例如,要根据原矩阵构建新矩阵,把其中各元素之和大于等于 10 的那些行选出来,而且只保留其中能够被 3 整除的那些元素。这个逻辑用列表推导来写,并不需要太多的代码,但是这些代码理解起来会很困难。
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
filtered = [[x for x in row if x % 3 == 0]
for row in matrix if sum(row) >= 10]
这里建议大家不要用刚才的写法来推导新的 list、dict 或 set,因为这样写出来的代码很难让初次阅读这段程序的人看懂。对于 dict 来说,问题尤为严重。因为必须得把键与值的推导逻辑都表达出来。
总之,在表示推导逻辑时,最多只应该写两个字表达式(例如两个 if 条件、两个 for 循环,或者一个 if 条件与一个 for 循环)。只要实现的比这还复杂,那就应该采用普通的 if 与 for 实现,并且可以考虑编写辅助函数(参见 第30条)。