可迭代的对象、迭代器和生成器
序列可以迭代的原因:iter函数
解释器需要迭代对象x时,会自动调用iter(x)
。
内置的iter函数有一下作用。
- 检查对象是否实现了
__iter__
,如果实现了就调用它,获取一个迭代器。 - 如果没有实现
__iter__
方法,但是实现__getitem__
方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素。 - 如果尝试失败,Python抛出TypeError异常,通常会提示“C object is not iterable”
任何Python序列都可迭代的原因是,它们都实现了__getitem__
方法。其实,标准的序列也都实现了__iter__
方法,之所以对__getitem__
方法做特殊处理,是为了向后兼容。
例子:
import re
import reprlib
RE_WORD = re.compile(r'\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __getitem__(self, index):
return self.words[index]
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
可迭代对象与迭代器的对比
可迭代的对象:
使用iter内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__
方法,那么对象就是可迭代的。序列都可以迭代:实现__getitem__
方法,而且其参数都是从零开始的索引,这种对象也可以迭代。
可迭代的对象与迭代器之间的关系:Python从可迭代的对象中获取迭代器
可迭代对象和迭代器要实现的接口
class Iterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __iter__(self):
while False:
yield None
class Iterator(Iterable):
__slots__ = ()
@abstractmethod
def __next__(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
def __iter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
if (any("__next__" in B.__dict__ for B in C.__mro__) and
any("__iter__" in B.__dict__ for B in C.__mro__)):
return True
return NotImplemented
其UML图如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mZ2H7wu0-1586264083644)(D:\learn\liuchangdepython\images\Iterable和Iterator.png)]
Iterable和Iterator抽象基类。以斜体显示的是抽象方法。具体的Iterable.__iter__
方法应该返回一个Iterator实例。具体的Iterator类必须实现__next__
方法。Iterator.__iter__
方法直接返回实例本身。
迭代器
迭代器是这样的对象:实现了无参数的__next__
方法,返回序列中的下一个元素;如果没有元素了,那么就抛出StopIteration
异常。Python中的迭代器还实现了__iter__
方法,因此迭代器也可以迭代。
典型的迭代器
迭代器模式:
- 访问一个聚合对象的内容而无需暴露它的内部表示
- 支持对聚合对象的遍历
- 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)
例子:
import re
import reprlib
RE_WORD = re.compile(r'\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
return SentenceIterator(self.words)
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
Sentence.__iter__
返回迭代器SentenceIterator
,而SentenceIterator
提供访问一个聚合对象的内容的实现。
可迭代的对象(如Sentence)一定不能是自身的迭代器。也就是说,可迭代的对象必须实现
__iter__
方法,但不能实现
__next__
方法
生成器函数
更pythonic 的方法:使用生成器函数替代SentenceIterator,不在单独定义一个迭代器类
import re
import reprlib
RE_WORD = re.compile(r'\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
for word in self.words:
yield word
生成器的工作原理
python函数的工作原理
def foo():
bar()
def bar():
pass
foo()
- python的解释器会用
PyEval_EvalFramEx(C语言)
函数去执行我们的foo函数. - 在运行foo函数的时候首先会创建一个栈帧(Stack frame), 这个栈帧是一个上下文, 也是一个对象.
- 栈帧会将foo函数变成一个字节码对象, 使用dis查看字节码
- 然后栈帧的上下文中去运行字节码(字节码是全局唯一的)
def foo():
bar()
def bar():
pass
import dis
print(dis.dis(foo))
2 0 LOAD_GLOBAL 0 (bar)
2 CALL_FUNCTION 0
4 POP_TOP
6 LOAD_CONST 0 (None)
8 RETURN_VALUE
None
- 当foo调用bar, 又会创建一个新的栈帧, 然后运行bar的字节码
- 所有的栈帧都是分配在堆的内存上, 如果不释放会一直存在, 所以栈帧可以独立于调用者存在, 就比如调用者foo不存在也没关系, 只要指针指向bar的栈帧就可以控制
import inspect
frame = None
def foo():
bar()
def bar():
pass
global frame
# 获取当前函数的栈帧并赋给全局变量frame
frame = inspect.currentframe()
foo()
print(frame.f_code.co_name)
>>> bar
caller_frame = frame.f_back
print(caller_frame.f_code.co_name)
>>> foo
- python解释器会编译字节码, 如果发现有yeild, 就会标记该函数, 然后再调用的时候会返回一个生成器对象. 而这个生成器对象实际上是把这个frame对象做了一个封装
- 生成器可以在任何时候、任何函数中恢复运行,因为它的栈帧并不在真正的栈中,而是堆中
- f_lasti指向“最后执行指令”的指针。初始化为 -1,意味着它没开始运行
惰性实现
设计 Iterator 接口时考虑到了惰性: next(my_iterator) 一次生成一个元素。 懒惰的反义词是急迫, 其实, 惰性求值(lazy evaluation) 和及早求值(eager evaluation) 是编程语言理论方面的技术术语。
re.finditer
函数是 re.findall
函数的惰性版本, 返回的不是列表, 而是一个生成器, 按需生成 re.MatchObject 实例。 如果有很多匹配, re.finditer
函数能节省大量内存。
def __iter__(self):
for match in RE_WORD.finditer(self.text):
yield match.group()
生成器表达式
生成器表达式可以理解为列表推导的惰性版本: 不会迫切地构建列表, 而是返回一个生成器, 按需惰性生成元素。 也就是说, 如果列表推导是制造列表的工厂, 那么生成器表达式就是制造生成器的工厂。
def __iter__(self):
for match in RE_WORD.finditer(self.text):
yield match.group()
yield from
如果生成器函数需要产出另一个生成器生成的值,传统的解决方法是使用嵌套的for循环
def chain(*iterables):
for it in iterables:
for i in it:
yield i
for i in chain([1, 3], (3, 4, 5)):
print(i)
使用yield from
def chain(*iterables):
for it in iterables:
yield from it
使用yield from it
完全替换了内层的for循环。 在这个示例中使用 yield from是对的, 而且代码读起来更顺畅, 不过感觉更像是语法糖。 除了代替循环之外, yield from 还会创建通道, 把内层生成器直接与外层生成器的客户端联系起来。 把生成器当成协程使用时, 这个通道特别重要, 不仅能为客户端代码生成值, 还能使用客户端代码提供的值。