Python笔记之列表解析

列表解析:函数式编程工具

列表解析(也叫做列表推导)是构造列表的快捷方式。列表解析与函数工具(如map和filter)相关,由于循环相关,所以我会在这提及一些。

列表解析与map

先举一个小小的例子来说明基础知识。

#Python内置ord函数会返回一个单个的ASCII整数编码(chr内置函数是其逆向过程)
>>>ord('s')
115

现在,假设我们想收集整个字符串中的所有字符的ASCII编码。也许我们第一个想到的办法就是写一个简单的for循环,并将结果附加在列表中:

>>>res = []
>>>for x in 'spam':
...    res.append(ord(x))
...
>>>res
[115, 112, 97, 109]

但是现在我们知道了map,我们能够使用一个单个的函数调用,而不必关心代码中列表的结构,从而简单实现

>>>res = list(map(ord, 'spam'))
>>>res
[115, 112, 97, 109]

尽管如此,我们能够通过列表解析表达式得到相同的结果——map把一个函数映射遍一个序列,列表解析把一个表达式映射遍一个序列:

>>>res = [ord(x) for x in 'spam']
>>>res
[115, 112, 97, 109]

列表解析在一个序列的值上应用一个任意表达式,将其结果收集到一个新的序列中并返回。从语法上来讲,列表解析是由方括号封装起来的。

上一个例子的效果与手动进行for循环和map调用相比,没有什么不同。然而,列表解析可以变得更方便,当我们希望对一个序列应用一个任意表达式的时候。

>>>[x ** 2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

这里我们收集了0~9数字的平方。和map调用差不多,我们也能够创建一个匿名函数来实现平方操作。

>>>list(map((lambda x: x ** 2), range(10)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

增加测试和嵌套循环

列表解析要比我们现在所介绍的更通用。我们可以在for循环之后编写一个if分支,用来增加选择逻辑。使用了if分支的列表解析能够当成内置的filter类似的工具,它们会在分支不是真的情况下跳过一些序列。

这里举一个选择出从0~4偶数的例子。同时为了对比,我们也在这也写一个filter版本创建了一个lambda函数。

>>>[x for x in range(5) if x % 2 == 0]
[0, 2, 4]


>>>list(filter((lambda x: x % 2 == 0), range(5)))
[0, 2, 4]


>>>res = []
   for x in range(5):
       if x % 2 == 0:
           res.append(x)    
>>>res  
[0, 2, 4] 

所有的这些都是用了求余操作符%,用来检测该数是否未偶数。filter调用与这里的列表解析相比也更短。尽管如此,在列表解析中能够混合一个if分支以及任意的表达式,从而赋予它通过一个单个的表达式,完成了一个filter和一个map相同的功效。

这次我们来收集0~9的偶数的平方。若在右边的if中得到的是假的话,for循环就会跳过这些数字,并且用左边的表达式来计算值。这个等效的map调用更多的工作来完成这一步分。我们需要在map迭代中混合filter选择过程,这使得表达式明显复杂得多。

>>>[x ** 2 for x in range(10) if x % 2 == 0]
[0, 4, 16, 36, 64]


list(map((lambda x: x**2), filter((lambda x: x % 2 == 0), range(10))))
[0, 4, 16, 36, 64]

实际上,列表解析还能够更加通用。你可以在一个列表解析中编写任意数量的嵌套的for循环,并且每一个都有可选的关联的if测试。

最后这里有一个复杂的多的列表解析工具,表明了在嵌套的for从语句附加if选择的作用。

>>>[(x, y) for x in range(5) if x % 2 == 0 for y in range(5) if y % 2 == 1]
[(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]

这个表达式排列了从0~4的偶数与0~4奇数的组合。其中if分句过滤了每个序列中需要进行迭代的元素。这里是一个等效的用语句编写的代码:

>>> for x in range(5):
...     if x % 2 == 0 :
...         for y in range(5):
...             if y % 2 == 1:
...                 res.append((x, y))
>>> res
[(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]

注意:如果你对一个复杂的列表解析理解有困难的话,你总是能够将列表解析的for和if分句在其中进行嵌套(将后来的分句缩进到右边),从而得到等效的语句

列表解析和其可读性

先来2段代码,你觉得哪一个示例中的代码更容易读懂?

>>>#示例1
>>>symbols = '$¢ǥ£€¤'
>>>codes = []
>>>for symbol in symbols:
...    codes.append(ord(symbol))

>>>codes
[36, 162, 485, 163, 8364, 164]
>>>#示例2
>>>symbols = '$¢ǥ£€¤'
>>>codes = [ord(symbol) for symbol in symbols]
>>>codes
[36, 162, 485, 163, 8364, 164]

凡是学过一点点Python的人都应该能够看懂示例1,但是我觉得如果掌握了列表解析的话示例2读起来会更方便.

列表解析可以帮助我们把一个序列或者是其他可以迭代类型中的元素过滤或是加工,让后再新建一个列表。Python内置的filter和map函数组合起来也能达到这一效果,但是可读性上打了不小的折扣。

列表解析同filter和map的比较

filter和map合起来能做的事情,列表解析也可以做,而且还不需要借助难以理解和阅读的lambda(匿名函数)表达式。在看示例3前我们先来了解一下filter和map。

filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。

以下是filter语法

filter(function, iterable)
function -- 判断函数。
iterable -- 可迭代对象

map() 会根据提供的函数对指定序列做映射。第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。

以下是map语法

map(function, iterable, ...)
function -- 函数
iterable -- 一个或多个序列

介绍完filter和map后我们来看示例3,使用列表推导和filter/map组合来创建同样的表单

>>>symbols = '$¢ǥ£€¤'
>>>beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
>>>beyond_ascii
[162, 485, 163, 8364, 164]
>>>beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))
>>>beyond_ascii
[162, 485, 163, 8364, 164]

个人觉得一般情况map/filter组合使用起来要比列表解析要快一点(而且使用map/filter感觉写的代码都要厉害一点点呢)。

 

关于Python列表解析的总结就到这里了。第一次在CSDN上写博客难免出错欢迎大家指出错误。希望各位Pythoner学业有成。

你可能感兴趣的:(Python笔记)