Contents
30.4.4. 函数返回列表时最好返回生成器¶
如果函数要返回的是个包含许多结果的序列,那么最简单的办法就是把这些结果放到列表中。例如,我们要返回字符串里每个单词的首字母所对应的下标。下面这种写法,会把每次遇到的新单词所在的位置追加(append)到存放结果的result列表中,在函数末尾返回这份列表。
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text):
if letter == ' ':
result.append(index + 1)
return result
address = "Four score and seven years ago..."
result = index_words(address)
print(result[:10])
index_words函数有两个缺点。
第一个缺点是,它的代码看起来有点杂乱。每找到一个新单词,它都要调用append方法,而调用这个方法时,必须写上result.append这样一串字符,这就把我们想要强调的重点,也就是这个新单词在字符串中的位置(index + 1)淡化了。
另外,函数还必须专门用一行代码创建这个保存结果的result列表,并且要用一条return语句把它返回给调用者。这样算下来,虽然函数的主体部分大约有130个字符(非空白的),但真正重要的只有75个左右。
这种函数改用生成器(generator)来实现会比较好。生成器由包含yield表达式的函数创建。下面就定义一个生成器函数,实现与刚才那个函数相同的效果。
def index_words_iter(text):
if text:
yield 0
for index, letter in enumerate(text):
if letter == ' ':
yield index + 1
address = "Four score and seven years ago..."
it = index_words_iter(address)
print(next(it))
print(next(it))
print(list(it))
it = list(index_words_iter(address))
print(it[:10])
这次的index_words_iter函数,比刚才那个函数好懂得多,因为它把涉及列表的那些操作全都简化掉了。
index_words函数的第二个缺点是,它必须把所有的结果都保存到列表中,然后才能返回列表。如果输入的数据特别多,那么程序可能会因为耗尽内存而崩溃。
用生成器函数来实现,它可以接受长度任意的输入信息,并把内存消耗量压得比较低。
def index_file(handle):
offset = 0
for line in handle:
if line:
yield offset
for letter in line:
offset += 1
if letter == ' ':
yield offset
with open("address.txt", "r") as f:
it = index_file(f)
result = itertools.islice(it, 0, 10)
print(list(result))
该函数运行时所耗的内存,取决于文件中最长的那一行所包含的字符数。把刚才那份输入数据存放到address.txt文件,让这个函数去读取并用它所返回的生成器构建一份列表,
可以看到跟原来相同的结果。
定义这种生成器函数的时候,只有一个地方需要注意,就是调用者无法重复使用函数所返回的迭代器,因为这些迭代器是有状态的。
要点:
用生成器来实现比让函数把结果收集合到列表里再返回,要更加清晰一些。
生成器函数所返回的迭代器可以产生一系列值,每次产生的那个值都是由函数体的下一条yield表达式所决定的。不管输入的数据量有多大,生成器函数每次都只需要根据其中的一小部分来计算当前这次的输出值。它不用把整个输入值全都读取进来,也不用一次就把所有的输出值全都算好。