复杂地方考虑使用生成器表达式


列表生成式的缺点

关于列表理解(详见第7项:使用列表表达式而不是mapfilter)方面的问题就在于其可能会给输入列表中的每一个只创建一个新的只包含一个元素的列表。这对于小的输入序列可能是很好用的,但是大的输入序列而言就很有可能导致你的程序崩溃。

例如:你想读取一个文件并且返回每行中的字符的数量。而是用列表表达式来处理这个问题的前提是内存中必须完全的包含这个文件的所有行。如果这个文件很大或者数据来自一个可能永远不会关闭的网络套接字数据流,此时列表表达式就会是一个大问题了。下面的这个例子就是指仅仅能处理小的输入序列的一个样例展示。

value = [len(x) for x in open('./my_file.txt','rb')]
print(values)
>>>
[100, 57, 15, 1, 12, 75, 5, 86, 89,11]

生成器表达式的好处

为了解决上面的这个问题,Python提供了一个generator expression(生成器表达式),其实就是列表表达式和生成器的一般化的体现。在程序运行的过程中,生成其表达式不实现整个输出序列,相反,生成其表达式仅仅是对从表达式中产生一个项目的迭代器进行计算,说白了就是每次仅仅处理一个迭代项,而不是整个序列。

生成器表达式通过使用类似于列表表达式的语法(在()之间而不是[]之间,仅此区别)来创建。这里,作者使用了一个和上例等价的生成器表达式。不同的是生成器表达式仅仅对当下的迭代项进行处理,并不做什么预先处理。

it = ( len(x) for x in open('/tmp/my_file.txt'))
print(it)
>>>
<generator object <genexpr> at 0x101b81480>

如果必须的话,返回的迭代器可以在生成器表达式中,一次被提前一步获得处理,用以产生下一个输出项。你的代码可以随心所欲的操作数据而无需顾虑内存使用率危机。

print(next(it))
print(next(it))
>>>
100
57

链式操作

生成器表达式的另一个强大的体现就在于其可以组合使用。这里,作者使用上面生成器表达式返回的迭代器来作为下一个生成器表达式的输出。

roots = ((x,x**0.5) for x in it)

每次推进这个迭代器,它就会推进内部的那个迭代器,通过循环,评估条件语句,输入输出等来产生一个类似于 多米诺骨牌 的效果。

print(next(roots))
>>>
(15,3.872983346207417)

Python中,执行类似的链式的生成器是很快的,当你正寻找一种构建功能的方法来处理一个巨大的输入流的时候,生成器表达式将会是一个很不错的选择。唯一的缺点就是通过生成器表达式返回的迭代器是有状态的,所以你必须很小心的来使用(详见第17项:当迭代参数的时候要小心应对)。


备忘录

  • 当遇到大输入事件的时候,使用列表表达式可能导致一些问题。
  • 生成器表达式通过迭代的方式来处理每一个列表项,可以防止出现内存危机。
  • 当生成器表达式 处于链式状态时,会执行的很迅速。

results matching ""

    No results matching ""