30.4.2. 推导逻辑的子表达式不要超过两个

除了最基本的用法外,列表推导式还支持多层循环。例如把矩阵转化成普通的一维列表,那么可以在推导时,使用两条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]
"""

多层循环还可以用来重制那种两层深的结构。例如,如果要根据二维矩阵里每个元素的平方值构建一个新的二维矩阵,那么可以采用下面的写法。

squared = [[x ** 2 for x in row] for row in matrix]
print(squared)
'''
[[1, 4, 9], [16, 25, 36], [49, 64, 81]]
'''

这看上去有点复杂,因为它把小的推导式逻辑[x**2 for x in row]嵌到了大的推导逻辑里面。大的推导逻辑用来决定新矩阵里的每一行,小的推导逻辑决定行中的每个元素。

my_lists = [[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]], ]
flat = [x for sublist1 in my_lists for sublist2 in sublist1 for x in sublist2]
print(flat)

'''
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
'''

此时,采用列表推导式来实现,其实并不会比传统的for循环节省多少代码。下面用for循环来写一次,这要比刚才那种三层矩阵的列表推导式更加清晰。

flat = []
for sublist1 in my_lists:
    for sublist2 in sublist1:
        flat.extend(sublist2)
print(flat)
'''
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
'''

推导的时候,可以使用多个if条件。如果这些if条件出现在同一层循环内,那么它们之间默认是and关系。

例如,如果要用原列表中大于4且是偶数的值来构建新列表,那么既可以连用两个if,也可以只用一个if

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
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]
print(b)
print(c)
assert b and c
assert b == c
'''
[6, 8, 10]
[6, 8, 10]
'''

在推导时,每一层的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]

print(filtered)
'''
[[6], [9]]
'''

总之,在表示推导逻辑时,最多只应该写两个子表达式(例如两个if条件、两个for循环,或者一个if条件与一个for循环)。

只要实现的逻辑比这还复杂,那就应该采用普通的if与for语句来实现,并且可以考虑编写辅助函数

要点:

推导的时候可以使用多层循环,每层循环可以带有多个条件。

控制推导逻辑的子表达式不要超过两个,否则代码很难读懂。