背景:今天放假在家,《python学习手册》不在身边,所以今天学习《Effective Python: 编写高质量Python代码的90个有效方法》第28条《控制推导逻辑的子表达式不要超过两个》,这本书已经是第二版了,第一版是《编写高质量python代码的59个有效方法》,这本书当时看第一版时还是很有收获的,现在出第二版了,就想再看一遍。
亿如要把一个矩阵(二维列表)转成普通的一维列表,那么可以在推导时,使用两条 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]
这样写简单易懂,这也正是多层循环在列表推导之中的合理用法。这种用法还可以稍做扩展,比如要把上面的矩阵换成每个数字的平方的矩阵。
>>> matrix = [[1,2,3],[4,5,6],[7,8,9]]
>>> squared = [[ x**2 for x in row] for row in matrix]
>>> squared
[[1, 4, 9], [16, 25, 36], [49, 64, 81]]
这个复杂了一些,但总体上还可以理解,但是如果再复杂一些的话,就会变得难以理解。
>>> 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]
>>> 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)
>>> 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]
>>> b
[6, 8, 10]
>>> c
[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]
>>> filtered
[[6], [9]]
>>>
虽然这些例子是专门造出来的,但现实中确实有类似的需求,这个时候,笔者不建议用上面的写法来推导新的 List,dict,或 set, 因为这样写出的来的代码会让初次阅读这段程序的人很难看懂。对于dict来说,尤其严重。
最后,作者的总结:
在用推导表达式时,最多只应该写两个子表达式(例如两个if条件,两个for循环,或者一个if条件与一个for循环)。要是逻辑比这还复杂,那就应该采用普通的if与for语句来实现,并且可以考虑编写辅助函数。