Python语言有一种独特的推导式语法,有点像语法糖,能够帮你在某些场合写出比较精简酷炫的代码,同时,它的性能可能会比咱们写循环要好。它主要用于初始化一个列表,也能够用于初始化集合和字典。python
列表推导式是一种快速生成列表的方式。它通常用“[]"括起来,例如数组
>>> [i for i in range(10)] [0,1, 2, 3, 4, 5, 6, 7, 8, 9]
这是一种最基本的用法,列表推导式先执行for循环,再把遍历的元素(或者对元素的一些计算表达式)做为列表的元素返回一个列表。app
>>> [i*i for i in range(10)] [0,1, 4, 9, 16, 25, 36, 49, 64, 81]
它就至关于ide
>>> l = [] >>> for i in range(10): ... l.append(i*i) ... >>>
咱们能够用列表推导快速初始化一个二维数组函数
m = [[0,0,0], [0,0,0], [0,0,0] ] n = [] for row in range(3): r = [] for col in range(3): r.append(0) n.append(r) print(n)
用下面的式子就能够获得这个二维数组性能
>>> [[0]*3 for i in range(3)] [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
列表推导式有不少种形式优化
这种生成的元素个数不会少,只是根据for循环的结果使用不一样的表达式code
# 若是i是5的倍数,结果是i,不然就是0 >>> [i if i % 5 == 0 else 0 for i in range(20)] [0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 10, 0, 0, 0, 0, 15, 0, 0, 0, 0] # 若是是偶数就加100,奇数就减100 >>> [i+100 if i % 2 == 0 else i-100 for i in range(10)] [100, -99, 102, -97, 104, -95, 106, -93, 108, -91]
这种会只取符合条件的元素,因此元素个数跟条件相关对象
# for循环的结果只选择是偶数的 >>> [i for i in range(10) if i % 2 == 0] [0, 2, 4, 6, 8] # for循环的结果只选择是2和3的倍数的 >>> [i for i in range(10) if i % 2 == 0 and i % 3 == 0] [0, 6] # for循环的结果只选择偶数,而且应用str函数 >>> [str(i) for i in range(10) if i % 2 == 0] ['0', '2', '4', '6', '8']
假如咱们展开一个二维矩阵,以下面的m,咱们能够用嵌套循环实现。blog
m = [[1,2,3], [4,5,6], [7,8,9] ] n = [] for row in m: for col in row: n.append(col) print(n)
用列表推导,最外层的for循环获得的row,能够在内层中使用
m = [[1,2,3], [4,5,6], [7,8,9] ] n = [col for row in m for col in row] print(n)
再好比下面这个例子
>>> [a + b for a in '123' for b in 'abc'] ['1a', '1b', '1c', '2a', '2b', '2c', '3a', '3b', '3c']
列表推导的用法比较灵活,咱们不必定要把全部的都掌握,可是要能看懂。
>>> dic = {"k1":"v1","k2":"v2"} >>> a = [k+":"+v for k,v in dic.items()] >>> a ['k1:v1', 'k2:v2']
集合推导的语法与列表推导同样,只是它是用”{}“,并且,集合会自动去重
>>> { i for i in range(10)} {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} >>> { 0 if i % 2 == 0 else 1 for i in range(10)} {0, 1}
字典推导的语法也与其余的相似,只不过在最前面的格式是key:value,并且也是会去重
>>> { i : i.upper() for i in 'hello world'} {'h': 'H', 'e': 'E', 'l': 'L', 'o': 'O', ' ': ' ', 'w': 'W', 'r': 'R', 'd': 'D'} >>> { str(i) : i*i for i in range(10)} {'0': 0, '1': 1, '2': 4, '3': 9, '4': 16, '5': 25, '6': 36, '7': 49, '8': 64, '9': 81}
既然用[]就能作列表推导,那用()是否是就能作元组推导了?不是的,由于()被用在了一种特殊的对象上:生成器(generator)。
>>> a = (i for i in range(10)) >>> print(a) <generator object <genexpr> at 0x000001A6100869C8> >>> type(a) <class 'generator'>
生成器是一个顺序产生元素的对象,只能顺序访问,只能前进,并且只能遍历一次。
可使用next()函数取下一个元素,取不到就会报StopIteration异常,也可使用for循环遍历。
生成式无法用下标访问,用next访问直到报异常
>>> a = (i for i in range(0,2)) >>> a[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'generator' object is not subscriptable >>> next(a) 0 >>> next(a) 1 >>> next(a) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
用for循环遍历
>>> a = (i for i in range(0,2)) >>> for i in a: ... print(i) ... 0 1
先用next访问,再用for循环
>>> a = (i for i in range(0,3)) >>> next(a) 0 >>> for i in a: ... print(i) ... 1 2
咱们能够加上list,tuple,set等作强转,可是list和set就不必了,若是想初始化成tuple,就用tuple作强转。强转的时候不须要再加多余的括号。
>>> a = tuple(i for i in range(0,3)) >>> a (0, 1, 2) >>> a = tuple( (i for i in range(0,3)) ) >>> a (0, 1, 2)
生成式是惰式计算的,就是你确实用到这个元素了,它才去计算,好处就是节省了内存,可是坏处是不能随机访问。
咱们用timeit模块去比较一下性能。
import timeit def getlist1(): l = [] for i in range(10000): l.append(i) return l def getlist2(): return [i for i in range(10000)] # 各执行10000次 t1 = timeit.timeit('getlist1()',"from __main__ import getlist1", number=10000) t2 = timeit.timeit('getlist2()',"from __main__ import getlist2", number=10000) print('循环方式:',t1) print('推导式方式:',t2)
执行结果以下:
循环方式: 5.343517699991935 推导式方式: 2.6003115000057733
可见循环的方式比推导式慢了一倍,为何会有这个问题呢?咱们直接反编译看这两个的区别,用dis模块能够反编译Python代码,产生字节码。
源代码及行数以下
getlist1的反编译以下,左边红色对应源代码的行数,蓝色圈内就是第6行代码对应的字节码,咱们能够看到,它有一个传参而且调用方法append的过程,调用函数的代价是比较大的。
再来看一下列表推断的反编译结果
首先从字节码数量上来比列表推断就比用循环调append要少的多,并且列表推断没有使用方法调用,直接用了这个指令LIST_APPEND,在Python官网上的解释是这样的。
实际上这个解释是有误导性,字节码中使用LIST_APPEND和在Python代码中调用append是彻底不同的,只不过这种底层的东西没有不少人关心,它们的功能是同样的。在2008年的时候就有人给Python代码提patch,但愿能自动将list.append()进行优化,直接优化成LIST_APPEND而不是经过函数调用,可是目前还没被采纳。
提出者但愿能在编译的时候加一些选项,好比像gcc可使用-O1,-O2等进行不一样级别的优化,可是目前CPython是没有这些选项的,由于大多数的Python开发者并不关心性能。
若是咱们把上面的列表换成集合或者字典,差异会更大,因此能用推导式的地方尽可能用推导式,能够提升性能。
其实这两个并不具有可比性,由于生成的结果并非一个东西。咱们能够很容易的预测,产生生成器的推导式性能要好于列表推导式,可是用的时候生成器就不如列表了。
import timeit def getlist1(): return [i for i in range(10000)] def getlist2(): return (i for i in range(10000)) # 各执行10000次 t1 = timeit.timeit('getlist1()',"from __main__ import getlist1", number=10000) t2 = timeit.timeit('getlist2()',"from __main__ import getlist2", number=10000) print('列表:',t1) print('生成器:',t2) def getlist11(): a = [i for i in range(10000)] sum = 0 for i in a: sum += i def getlist22(): a = (i for i in range(10000)) sum = 0 for i in a: sum += i # 各执行10000次 t1 = timeit.timeit('getlist11()',"from __main__ import getlist11", number=10000) t2 = timeit.timeit('getlist22()',"from __main__ import getlist22", number=10000) print('列表:',t1) print('生成器:',t2)
执行结果:
列表: 2.5977418000111356 生成器: 0.006076899997424334 列表: 6.336311199993361 生成器: 9.181903699995019
生成器产生的性能远大于列表,可是遍历的时候不如列表,可是整体上看好像生成器好。不过不要忘了,生成器不能随机访问,并且只能用一次。因此这两种对象,就是在合适的地方用合适的类型,不必定哪种比哪种更好。