如果函数要产生一系列结果,那么最简单的做法是把这些结果放在一份列表(list)中,并将其返回给函数调用者。例如我们要获得一份文本中每个单词的长度。
def get_word(text):
result = []
for i, letter in enumerate(text.split(' ')):
result.append(len(letter))
return result
输入验证下:
text = 'Five score years ago, a great American, in whose symbolic shadow we stand today, signed the Emancipation Proclamation.'
print(get_word(text))
输出结果:
[4, 5, 5, 4, 1, 5, 9, 2, 5, 8, 6, 2, 5, 6, 6, 3, 12, 13]
这个get_word()函数本身没有问题,但是有2个方面值得注意,一是这个函数稍显拥挤,需要预先定义一个列表,然后在循环中添加元素,最后返回,并不是很简练,当然这不是什么大问题。二是当你有大量数据并把所有值放在内存时,这种处理方式可能并不是很好,例如text文本非常大,有好几个GB,就无法使用这种方式了。
这时,需要用到生成器(generator),生成器就是使用yield表达式的函数,使用很简单,直接把你函数需要返回的列表内容前面加上yield即可。如上面那个函数,改写如下:
def gen_get_word(text):
for i, letter in enumerate(text.split(' ')):
yield len(letter)
可以看到,这个改写后的函数清晰了许多,没有列表的定义、添加元素、返回列表等,列表中的元素,现在都分别传给yield了。看看试验结果:
print(list(gen_get_word(text)))
输出:
[4, 5, 5, 4, 1, 5, 9, 2, 5, 8, 6, 2, 5, 6, 6, 3, 12, 13]
这和最开始的函数得到的结果是一致的。
生成器的优点是它无须将对象的所有元素都存入内存后,才开始进行操作。而是仅在迭代至某个元素时才会将该元素放入内存。这个特点使得它特别适合用于遍历一些巨大序列对象,例如大文件、大集合、大字典等。这个特点也被称为延迟计算或惰性求值(lazy evaluation),可以有效的节省内存空间。
例如,如果直接对文件对象调用read()方法,会导致不可预测的内存占用。可以用生成器读取固定长度的缓冲区,不用将文件一次性读入内存。
def read_file(path):
with open(path, 'rb') as f:
while True:
block = f.read(1024)
if block:
yield block
else:
return
总结下:
- 所有需要返回序列对象的函数都可以使用生成器。
- 使用生成器比收集结果放入列表然后返回的方式更加简练清晰。
- 生成器函数返回的是一个迭代器,可以把传给yield些值,逐次产生出来。
- 生成器不会影响内存的消耗,特别适合于处理大数据文件、大的数据对象等。