列表解析式也称之为列表推导式,即List Comprehensions,是Python内置的非常简单却强大的可以用来创建列表的生成式。本质上就是用列表来构建列表,通过对已有列表中的每一项应用一个指定的表达式来构建出一个新的列表。列表解析式的优势是编码简单,运行起来很快。
列表解析式的三个核心要素是:
1. 作用于输入序列的运算表达式;
2. 对输入序列的循环表达式;
3. 对输入序列的过滤条件,其中过滤条件是可选的。
举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11)):
1 2 |
>>> list(range(11)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
但如果要生成[1×1, 2×2, 3×3, …, 10×10]怎么做?方法一是循环:
1 2 3 4 5 6 |
>>> L = [] >>> for x in range(1,11): ... L.append(x * x) ... >>> L [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] |
但是循环太繁琐,且效率不高。而解析式则可以用一行语句代替循环生成上面的list:
1 2 3 4 5 |
>>> [x for x in range(1, 11)] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> [x * x for x in range(1, 11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] |
写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。
for循环后面还可以加上if判断,起到过滤的作用,比如我们可以仅筛选出偶数的平方:
1 2 |
>>> [x * x for x in range(1, 11) if x % 2 == 0] [4, 16, 36, 64, 100] |
虽然用列表表达式表示出这段代码很短,但是其可读性确实很糟糕。
列表解析式也支持多层循环,下面是一个两层循环,可以生成全排列:
1 2 |
>>> [m + n for m in 'ABC' for n in 'XYZ'] ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ'] |
运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:
1 2 |
>>> import os >>> [d for d in os.listdir('.')] |
for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value:
1 2 3 4 5 6 7 8 9 |
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' } >>> d.items() [('y', 'B'), ('x', 'A'), ('z', 'C')] >>> for k, v in d.items(): ... print(k, '=', v) ... ('y', '=', 'B') ('x', '=', 'A') ('z', '=', 'C') |
因此,列表生成式也可以使用两个变量来生成list:
1 2 3 |
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' } >>> [k + '=' + v for k, v in d.items()] ['y=B', 'x=A', 'z=C'] |
最后把一个list中所有的字符串变成小写:
1 2 3 |
>>> L = ['Hello', 'World', 'IBM', 'Apple'] >>> [s.lower() for s in L] ['hello', 'world', 'ibm', 'apple'] |
使用列表解析的好处有两点:
首先,代码变短了、可读变强了。
其次,性能变强了。
下面给出普通for循环生成的列表和列表解析式生成的列表,使用timeit模块进行一下性能测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 普通列表生成; def raw(lst): plus_one = [] for x in lst: plus_one.append(x+1) return plus_one # 列表解析生成; def comprehensions(lst): return [x + 1 for x in lst] # 两个方式结果是一样的; >>> raw(range(10)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> comprehensions(range(10)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
timeit模块这里使用timeit模块的timeit方式进行性能测试
1 |
timeit(stmt='pass', setup='pass', timer=<built-in function time>, number=1000000) |
stmt:要执行的那段代码。
setup:执行代码的准备工作,初始化代码或构建环境导入语句,不计入时间,一般是import之类的。
timer:这个在win32下是time.clock(),Linux下是time.time(),默认的,不用管。
number:要执行stmt多少遍,默认一百万次。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 测试raw函数; >>> if __name__ == '__main__': ... import timeit ... print(timeit.timeit("raw(range(10))", setup="from __main__ import raw")) ... 1.6948029995 # 测试comprehensions函数; >>> if __name__ == '__main__': ... import timeit ... print(timeit.timeit("comprehensions(range(10))", setup="from __main__ import comprehensions")) ... 1.03069210052 |
另外,在ipython中也提供了一个简便的测试速度的方式,叫%timeit,操作方式如下:
1 2 3 4 5 |
In [5]: %timeit raw(range(10)) 1000000 loops, best of 3: 1.67 µs per loop In [6]: %timeit comprehensions(range(10)) 1000000 loops, best of 3: 1.05 µs per loop |
由测试结果可以看出,列表解析比普通循环生成的列表速度要快很多。
除了列表之外,字典和集合(Python 3好像不支持集合了,或者换了别的方式,暂时没太了解)同样支持这种使用方法,在语法差不多,只不过就是把中括号该改成花括号,产生的结果是集合和字典而已。下面直接举例说明。
下面使用字典解析式把字符串以及其长度建成字典。
1 2 3 |
>>> strings = ['import','is','with','if','file','exception'] >>> {key: val for val,key in enumerate(strings)} {'import': 0, 'is': 1, 'with': 2, 'if': 3, 'file': 4, 'exception': 5} |
再来一个快速更换字典的key和value。
1 2 3 |
>>> a = {'a': 10, 'b': 34} >>> {v: k for k, v in a.items()} {10: 'a', 34: 'b'} |
我强烈的建议你在遇到以下情形的时候避免使用列表表达式。那就是代码对别人来说难于理解,通过使用列表表达式来减少的代码行数不足以胜过其即将带来的麻烦时,请不要使用列表表达式。其次在一个列表解析式中避免使用超过两个的表达式,这些表达式可以是条件语句,循环语句,或者一个判断一个循环。只要事情变得比这种情况还要复杂,就不应该使用列表表达式了,而是应该使用常规的语句来实现相同的业务逻辑。