每天5分钟玩转Python(11) - 生成器(上)
这篇开始要展示python这门语言真正的魅力所在了。python有一些高级功能, 让我们的代码写起来超级爽,所以才会有这么多人喜欢它。
这篇先介绍生成器这个东东,学会你就知道它有多强大了,不过对于生成器的讲解稍微有点长, 可能看完不止5分钟,所以我分了上下两篇。建议读者耐心看完,这是进阶的必经之路。
列表推导
在讲生成器之前,先讲讲python里面常用的几个常见的推导式:
列表推导式(list comprehension)
1 | my_list = [f(x) for x in sequence if cond(x)] |
返回一个新的列表,列表中每个元素是原来列表中满足cond(x)
条件的元素经过函数f(x)
转换后的值。
字典推导式(dictionary comprehension)
1 | my_dict = {k(x): v(x) for x in sequence if cond(x)} |
集合推导式(set comprehension)
1 | my_set = {f(x) for x in sequence if cond(x)} |
几种推导式原理都一样,我只讲列表推导就可以了。它是python内置用来生成新列表的语法。 除了上面的单层循环,你还能使用双层循环。比如打印9*9乘法表。一句话搞定:
1 | print("\n".join("\t".join(["{} * {} = {}".format(y, x, x * y) for y in range(1, x + 1)]) for x in range(1, 10))) |
"\t".join(["{} * {} = {}".format(y, x, x*y) for y in range(1, x+1)])
这句是一个将以推导式的形式生成的列表转换成字符串,以制表符分隔;单独这一句代码不能执行, 因为x还未赋值,我们可以假设它有一个值,这一行代码最终结果是打印99乘法表的每一行。- 剩下的代码则是外层循环,列表推导式,转换字符串,以换行符分隔,跟第1步一样。
注意外层的join操作对象并没有再使用
[]
转成列表,加上也可以。
输出结果:
1 | 1 * 1 = 1 |
生成器的优势
列表推导会将所有元素都生成后填充到列表中,这样当元素非常多的时候就会很耗内存。 如果列表元素不需要一次性就初始化,而是在使用它的时候再算出来,这就是经常说的懒加载技术。 这种一遍循环一遍计算元素值得机制称为生成器(generator)。
创建生成器
创建生成器的方式有两种方式。
第一种方式是将列表推导的[]
改成()
就可以了。比如
1 | _list = [x for x in range(6)] |
打印结果如下:
1 | [0, 1, 2, 3, 4, 5] |
注意下面的是一个对象,类型为generator
另外一种方式是通过yield
关键字定义。如果在python的函数中出现了一个yield
关键字,
那么python不会把它当做普通函数看待了,而是将它当做是一个生成器generator。
比如我定义一个斐波拉契数列的函数:
1 | def fib(max): |
使用生成器
拿到生成器对象后,我怎么使用这个生成器呢?很简单,可以通过不断的调用next()方法来获取它的下一个元素:
1 | _list_generator = (x for x in range(3)) |
结果如下:
1 | 0 |
怎么到了第四行出现一个异常了呢?因为我把它所有的元素都消耗完了,只有3个元素。第四次再访问就会抛StopIteration
异常出来。
这一篇讲了生成器的基本用法,下一篇再继续深入讲解yield
关键字以及python中的协程原理。