一. 返回系列结果的函数
如果函数要返回一系列结果,我们常见的方法就是将结果放到一份列表中,然后返回给调用者。比如下面的函数,返回字符串中每个单词的首字母在真个字符串中的索引:
def get_word_index(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text):
if letter == ' ':
result.append(index + 1)
return result
运行结果:
>> text = 'Python is a very popular programming language.'
>> get_word_index(text)
[0, 7, 10, 12, 17, 25, 37]
上述的结果完全符合我们的预期,但 get_word_index
函数不够简洁。下面我们尝试使用生成器来实现:
def word_index_iter(text):
if text:
yield 0
for index, letter in enumerate(text):
if letter == ' ':
yield index + 1
运行结果:
>> list(word_index_iter(text))
[0, 7, 10, 12, 17, 25, 37]
改写之后,不仅运行结果符合要求,由于不需要和 result
列表交互,函数也变得非常简洁。下面我们就来详细学习下生成器吧~
二. 生成器
生成器是指使用 yield
表达式的函数,调用生成器函数时,它并不会真的运行,而是会返回迭代器。每次在这个迭代器上面调用内置的 next
函数时,迭代器就会把生成器推进到下一个 yield
表达式那里。生成器传给 yield
的值均会由迭代器返回给调用者。
此外,如果输入量非常大,使用列表作为返回值,那么程序就有可能耗尽内存并崩溃。相反,使用生成器之后,则可以应对任意长度的输入数据。
例如,下面这个生成器函数可以获取文件中单词的索引,而不管文件内容多大,该函数执行时消耗的内存,只由单行的文本长度决定:
def file_index_iter(handler):
offset = 0
for line in handler:
if line:
yield offset
for letter in line:
offset += 1
if letter == ' ':
yield offset
with open('test_generator.txt') as f:
it = file_index_iter(f)
print(list(it))
其中 test_generator.txt 中的内容如下:
Python is a very popular programming language.
Python is very good at data analysis, web development and Linux system management.
运行结果:
[0, 7, 10, 12, 17, 25, 37, 47, 54, 57, 62, 67, 70, 75, 85, 89, 101, 105, 111, 118]
三. 注意
下面这句话特别重要:生成器函数返回的迭代器,是由状态的,及调用者不应该反复使用它。我们那 word_index_iter
来说明:
>> i = word_index_iter(text)
>> list(i)
[0, 7, 10, 12, 17, 25, 37]
>> list(i)
[]
如果想重复调用,请将其封装成容器:
class WordIndex:
def __init__(self, text):
self.text = text
def __iter__(self):
if self.text:
yield 0
for index, letter in enumerate(self.text):
if letter == ' ':
yield index + 1
运行结果:
>> i = WordIndex(text)
>> list(i)
[0, 7, 10, 12, 17, 25, 37]
>> list(i)
[0, 7, 10, 12, 17, 25, 37]
关于上述自定义容器的实现原理,小编的另外一篇文章做了详细介绍,链接奉上:https://www.jianshu.com/p/0bae644d393a