Python中的列表生成式(List Comprehensions)和列表生成器(Generator),是Python提供的两个高级应用机制。
生成式是一种简写机制,坚持了龟叔的“Python要简单优雅”的设计理念。
生成器虽然翻译成中文以后只有一字之差,但是实现的机制和生成式已经完全不同了。它存储的是一个算法,而非具体数据。如何理解呢?听我娓娓道来。
列表生成式是Python提供的一种简易的列表生成表达式。对于一些极其规律且简单逻辑的列表生成算法,可以用列表生成式一行搞定。
它的语法格式是:
[列表元素模式(空格)列表元素生成算法(数据来源)表达]
举个例子:
有如下需求:要生成[1x1, 2x2, 3x3, ..., 10x10]这样的一个list
传统的思路是:
L = []
for x in range(1, 11):
L.append(x * x)
接下来是列表生成式写法:
[x * x for x in range(1, 11)]
是不是简洁了很多?我想,不用我解释,应该也浅显易懂吧。
如果翻译成白话文,上面这条语句的意思是:
生成这样一个列表,他的每一个元素都是x*x,其中,x的值是这样的 for x in range(1, 11)
列表生成式中的for循环后可加if判断。
例如:求偶数平方数列。可以用列表生成式来书写
[x * x for x in range(1, 11) if x % 2 == 0]
.例如:生成'ABC‘和'XYZ'的完全结合子串
[m + n for m in 'ABC' for n in 'XYZ']
列表生成式也可以使用两个变量来生成list
d = {'x': 'A', 'y': 'B', 'z': 'C' }
[k + '=' + v for k, v in d.items()]
.列表中所有字符串变小写
L = ['Hello', 'World', 'IBM', 'Apple']
[s.lower() for s in L]
.以及利用if语句是算法更灵活,如下例中,合理屏蔽掉18这个非字符串数据:
L1 = ['Hello', 'World', 18, 'Apple', None]
L2 = [s.lower() for s in L1 if isinstance(s, str) == True]
有一句至理名言,必须牢记:
生成器保存的是算法。
生成器的创建很简单。将列表生成式的[]换成()即可
例如,创建一个能够生成x平方的列表的生成器如下:
g = (x * x for x in range(10))
生成器保存的是生成算法,而非数据本身,因此,它与生成式不同,它并不携带任何数据,因此,想用print的形式是无法输出任何数据的,只能输出一个表示本对象是一个生成器对象的提示。如下:
g = (x * x for x in range(10))
print(g)
结果为:
at 0x00000252A2AC1690>
生成器像是一个携带着数据的宝盒,需要一把钥匙触发机关,他才会持续不断的根据算法产生数据并输出。这种触发算法的钥匙最常用的两个是:1.next方法,2.for循环的遍历。
next方法会触发一次算法的计算,一次next就会激发一次算法的计算产生下一个数据。例如:
对于生成器:
g = (x * x for x in range(10))
如果:
next(g)
next(g)
则会输出:
1
4
两个数据。
使用for循环遍历,可以持续触发算法,并输出所有算法能覆盖的数据
例如:
对于生成器:
g = (x * x for x in range(10))
如果:
for n in g:
print(n)
则会输出所有的数列元素1,4,9,16等。
当推算算法比较复杂,可以用函数来实现。
如Fibonacci数列,很显然,这个算法写不到一行上去。没办法用小括号()的形式来展现算法,可以使用函数的形式来实现生成器。Fibonacci数列的普通函数实现如下:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
这有个小细节,其中的
a,b = b, a + b的意义是,先将右边的值全部算出,再分别赋值给左边的变量。
相当于:
temp1 = b
temp2 = a + b
a = temp1
b = temp2
而不是:
b = a + b
a = b
自己体会一下区别。
上面的Fibonacci数列的普通函数稍加改造,就可以变成一个生成器函数。
关键在于yield关键字。
先上案例程序:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
将普通函数中的print语句,换成yield。
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。
函数是顺序执行,遇到return或最后一行返回。
包含yield关键字的生成器函数的执行逻辑如下:
Generator每次调用next()时候执行,遇到yield返回。再次执行,从上一次返回的yield处继续执行。
刚刚构建的生成器fib,遇到next调用时,就生成一个fibnacci数列的元素。
g = fib(10)
next(g)
next(g)
next(g)
next(g)
执行结果为:
1
1
2
3
再举一个例子:
写一个generator依次返回数字1,3,5
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
可以利用next择机输出(或者说延迟输出)想要的数据
o = odd()
next(o)
next(o)
next(o)
得到的结果为:
step 1
1
step 2
3
step 3
5
如果再使用next(o),则会产生异常。
Traceback (most recent call last): File "
生成器和生成式,好像差不多啊。
生成器函数和普通函数,好像也差不多啊。
生成器的最大用途,是可以让算法的输出滞后,在用户需要的时机,缓缓输出。可以通过上面的例子来推敲和理解一下。
生成器让类似于“自然数集”这样的无限数据生成算法,有了有限输出的空间。这一点尤为关键。因为算法必须是有限的,无限的算法是无意义的。毕竟,无论是时间还是空间,都必须是有限的,才能实施下去。