列表生成器

今天在微信上看到一个帖子:

在回答这个问题之前,我们先来聊聊几个基本的问题

一、列表生成式

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。

举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用 range(1, 11)

但如果要生成[1×1, 2×2, 3×3, …, 10×10]怎么做?方法一是循环:

但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

还可以使用两层循环,可以生成全排列:

其他例子:

二、生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。

要创建一个generator,有很多种方法。

方法一:把一个列表生成式的 []改成 ()

创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过generator的next()方法:

我们讲过,generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
当然,上面这种不断调用next()方法实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

方法二:yield

著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, …

要把fib函数变成generator,只需要把print b改为yield b就可以了:

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next()就报错。

同样的,把函数改成generator后,我们基本上从来不会用next()来调用它,而是直接使用for循环来迭代:

三、变量作用域

回到最开始的问题。这个问题是变量作用域问题,在 gen =( x for _ in xrange (10)) 中 gen 是一个 generator ,在 generator 中变量有自己的一套作用域,与其余作用域空间相互隔离。因此,将会出现这样的 NameError:name ‘ x ‘ is not defined 的问题,那么解决方案是什么呢?答案是:用 lambda 。

或者,也可以这样:

参考资料:

廖雪峰–列表生成式 
廖雪峰–生成器
http://blog.csdn.net/yejianyun1/article/details/52640838

发表评论

电子邮件地址不会被公开。 必填项已用*标注