Bootstrap
在我们处理一个新序列,我们不想在内存中放置一个新的列表、集合或者字典。因为可能数据量比较大,不能将所有数据都放到内存中。我们可能只做一次遍历,而不关心是否要创建一个最终的对象容器。这时我们就可以使用生成器了。下面我们看一下生成器表达式和生成器的用法。

生成器表达式

如果我们对G量级的日志文件做处理。使用for循环,可以一次只处理一行而不需要将下一行读入内存。并且不会创建一个最终的容器对象。我们就可以创建一个生成器表达式,语法很简洁。下面看一下代码,

import sys
inname = sys.argv[1]
outname = sys.argv[2]
with open(inname) as infile:
with open(outname, "w") as outfile:
warnings = (l for l in infile if 'WARNING' in l)
for l in warnings:
outfile.write(l)

上面使用生成器表达式来过滤所有的警告,括号包裹起来的代码就是生成器表达式。如果文件比较小就怎么样处理都可以,文件比较大,行数比较多,就推荐用生成器表达式来处理。

生成器

yield是生成器的关键字,当在一个函数中看到yield,它会取出这个函数并把它包装在一个对象中。可以认为yield语句和return语句是一样的,它退出一个函数并且返回一行。但与return不同的是,当再次被调用的时候,会从上次结束的地方启动,也就是从yield语句后面的行开始。下面看一下通过yield删除日志文件中WARNING列的例子,

import  sys
iname, outname = sys.argv[1:3]
def warnings_filter(p) :
for l in p:
if 'WARNING' in l:
yield l.replace('\tWARNING',' ')
with open(inname) as infile:
with open(outname, "w") as outfile:
filter = warnings_filter(infile)
for l in filter:
outfile.write(l)

带有yield关键字函数返回的是一个生成器对象,这个对象有__iter__和__next__方法,每当__next__被调用,生成器都会执行到yield语句。然后它会返回yield的值。当__next__下一次被调用时,它会在上次停止的地方继续执行。

yield方法返回生成器对象

>>> help(warnings_filter([]))
Help on generator object:
warnings_filter = class generator(object)
| Methods defined here:
|
| __del__(...)
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __iter__(self, /)
| Implement iter(self).
|
| __next__(self, /)
| Implement next(self).
|
| __repr__(self, /)
| Return repr(self).
|
| close(...)
| close() -> raise GeneratorExit inside generator.
|
| send(...)
| send(arg) -> send 'arg' into generator,
| return next yielded value or raise StopIteration.
|
| throw(...)
| throw(typ[,val[,tb]]) -> raise exception in generator,
| return next yielded value or raise StopIteration.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| gi_code
|
| gi_frame
|
| gi_running
|
| gi_yieldfrom
| object being iterated by yield from, or None

通过生成器(yield)实现,可读性可以,代码也很简洁。如果只是用for循环或面向对象方式实现,代码应该都会稍微复杂一点。