设计 Iterator 接口时考虑到了惰性:next(my_iterator) 一次生成一个元素。懒惰的反义 词是急迫,其实,惰性求值(lazy evaluation)和及早求值(eager evaluation)是编程语言 理论方面的技术术语。
目前之前博客的几版 Sentence
类都不具有惰性,因为 __init__
方法急迫地构建好了文本中的 单词列表,然后将其绑定到 self.words
属性上。这样就得处理整个文本,列表使用的内存 量可能与文本本身一样多(或许更多,这取决于文本中有多少非单词字符)。如果只需迭代前几个单词,大多数工作都是白费力气。
只要使用的是 Python 3,思索着做某件事有没有懒惰的方式,答案通常都是肯定的,惰性实现才更符合python语言的规范。
re.finditer
函数是 re.findall
函数的惰性版本,返回的不是列表,而是一个生成器,按 需生成 re.MatchObject
实例。如果有很多匹配,re.finditer
函数能节省大量内存。我们 要使用这个函数让 Sentence
类变得懒惰,即只在需要时才生成下一个单词。
示例:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
def __repr__(self):
# reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表示形式
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
# 迭代 self.words
for match in RE_WORD.finditer(self.text):
yield match.group()
if __name__ == '__main__':
s = Sentence('I love python')
ss = iter(s)
print(ss)
print(next(ss))
print(next(ss))
print(next(ss))
print(list(ss))
生成器函数已经极大地简化了代码,但是使用生成器表达式甚至能把代码变得更简短。
简单的生成器函数,如前面的 Sentence
类中使用的那个,可以替换成生成 器表达式。
生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成 器,按需惰性生成元素。也就是说,如果列表推导是制造列表的工厂,那么生成器表达式 就是制造生成器的工厂,其实就是将列表推到中的[]
换成()
,就行了。
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
def __repr__(self):
# reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表示形式
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
# 迭代 self.words
# for match in RE_WORD.finditer(self.text):
# yield match.group()
return (match.group() for match in RE_WORD.finditer(self.text))
if __name__ == '__main__':
s = Sentence('I love python')
ss = iter(s)
print(ss)
print(next(ss))
print(next(ss))
print(next(ss))
print(list(ss))
唯一的区别是 __iter__
方法,这里不是生成器函数了(没有 yield
),而是使 用生成器表达式构建生成器,然后将其返回。不过,最终的效果一样:调用 __iter__
方法 会得到一个生成器对象。
生成器表达式是语法糖:完全可以替换成生成器函数,不过有时使用生成器表达式更便利。