无名 发表于 2022-5-8 17:04:19

【LSP】python生成


http://cdn.u1.huluxia.com/g4/M01/5A/E2/rBAAdl9u0--AWOddAACt4WdlvYs524.jpg
迭代是数据处理的基石,在扫描内存无法装载的数据集时,我们需要一种惰性获取数据的能力(即一次获取一部分数据到内存)。在Python中,具有这种能力的对象就是迭代器。生成器是迭代器的一种特殊表现形式。

个人认为生成器是Python中最有用的高级特性之一(甚至没有之一)。虽然初级编码中使用寥寥,但随着学习深入,会发现生成器是协程,异步等高级知识的基石。Python最有野心的asyncio库,就是用协程砌造的。

注:生成器和协程本质相同。PEP342(Python增强提案)增加了生成器的send()方法,使其变身为协程。如此之后,生成器生成数据,协程消费数据。虽然本质相同,但是由于从理念上说协程跟迭代没有关系,并且纠缠生成器和协程的区别与联系会引爆自己的大脑,所以应该将这两个概念区分。此处说本质相同意为:理解生成器原理之后,理解增加了send方法,但是实现方式几乎相同的协程会更加轻松(这段话看不懂没有关系,船到桥头自然直,学到协程自然懂)。

Python的一致性是其最迷人的地方。了解了Python生成器,迭代器的实现。就会对Python的一致性设计有更加强烈的感知。本文读完之后,遇到面试官提问为什么列表可以迭代,字典可以迭代,甚至文本文件都可以迭代时,你就可以稳(huang)得一批。

阅读本文之前,如果你对Python的一致性有一些了解,如鸭子类型,或者Cpython的PyObject结构体,那真是太棒了。不过鉴于笔者深厚的文字功底,没有这些知识也不打紧。

干货儿
迭代器

在学习生成器之前,先要了解迭代器。顾名思义,迭代器即具有迭代功能的对象。在Python中,可以认为迭代器可以通过不断迭代,产生出一个又一个的对象。

可迭代对象和迭代器

Python的一致性是靠协议支撑的。一个对象只要遵循以下协议,它就是一个可迭代对象或迭代器。

Python中的一个对象,如果实现了iter方法,并且iter方法返回一个迭代器,那么它就是可迭代对象。如果实现了iter和next方法,并且iter方法返回一个迭代器,那么它就是迭代器(有点绕,按住不表,继续学习)。

注:如果对象实现了__getitem__方法,并且索引从0开始,那么也是可迭代对象。此hack为兼容性考虑。只需切记,如果你要实现可迭代对象和可迭代器,那么请遵循以上协议。

可迭代对象的iter返回迭代器,迭代器的iter方法返回自身(也是迭代器),迭代器的next方法实现迭代功能,不断返回下一个元素,或者在元素为空时raise一个StopIteration终止迭代。

可迭代对象与迭代器的关系

话不多说,上代码。http://cdn.u1.huluxia.com/g4/M01/5A/E2/rBAAdl9u0--AZy24AAK44jAEaXs970.jpg
上述代码中,实现了可迭代对象Iterable和迭代器Iterator。遵循协议规定,Iterable实现了iter方法,且iter方法返回迭代器Iterator实例,迭代器实现了iter方法和next方法,iter返回自身(即sel,迭代器本身f),next方法返回迭代器中的元素或者引发StopIteration异常。运行上述代码,会看到#2处的输出。

通过上述代码迭代一个对象显得十分啰嗦。比如在Iterable中,iter必须要返回一个迭代器。为什么不能直接用Iterator迭代元素呢?假设我们通过迭代器来迭代元素,将上述代码中的#1处如下代码:http://cdn.u1.huluxia.com/g4/M01/5A/E2/rBAAdl9u0_CACkoyAAEa1CnnKSc903.jpg
运行上述代码,会看到#6处的输出。疑惑的是,#3和#4处运行了两次for循环,结果只打印一遍所有元素。解释如下:

上述代码中,ins是一个Iterator迭代器对象。那么ins符合迭代器协议:每次调用next,会返回下一个元素,直到迭代器元素为空,raise一个StopIteration异常。

#3处第一次通过for循环迭代ins,相当于不断调用ins的next方法,不断返回下一个元素,输出如#6所示。当元素为空时,迭代器raise了StopIterator。而这个异常会被for循环捕获,不会暴露给用户,所以我们就认为数据迭代完成,并且没有出现异常。

迭代器ins内的元素已经被#3处的for循环消耗完,并且raise了StopIteration(只不过被for循环捕获静默处理,没有暴露给用户)。此时ins已经是元素消耗殆尽的“空”状态。在#4处第二次通过for循环迭代ins,因为ins内的元素为空,继续调用ins的next方法,那么还是会raise一个StopIteration,而且又被for循环静默处理,所以没有异常,也没有输出。

接下来,#5处通过next方法获取ins的下一个元素,同上,继续raise一个StopIteration异常。由于此处通过next调用而不是for循环,异常不会被处理,所以抛出到用户层面,即#7输出。

http://cdn.u1.huluxia.com/g4/M01/5A/E2/rBAAdl9u0_GADad8AANvUZOUE_M909.jpg
页: [1]
查看完整版本: 【LSP】python生成