容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)众多概念,有点复杂,整理一下。
详情可以看一篇文章:https://foofish.net/iterators-vs-generators.ht
0x1容器(container)
容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in, not in关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特例,并不是所有的元素都放在内存,比如迭代器和生成器对象)
在Python中,常见的容器对象有:
1 |
list, deque, .... |
容器比较容易理解,因为你就可以把它看作是一个盒子、一栋房子、一个柜子,里面可以塞任何东西。从技术角度来说,当它可以用来询问某个元素是否包含在其中时,那么这个对象就可以认为是一个容器,比如 list,set,tuples都是容器对象:
1 |
>>> 4 not in [1, 2, 3] |
尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力,当然并不是所有的容器都是可迭代的,比如:Bloom filter,虽然Bloom filter可以用来检测某个元素是否包含在容器中,但是并不能从容器中获取其中的每一个值,因为Bloom filter压根就没把元素存储在容器中,而是通过一个散列函数映射成一个值保存在数组中。
0x2可迭代对象(iterable)
0x2.1可迭代对象的理解
迭代器协议是指:对象需要提供next方法,它要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代
可迭代对象就是:实现了迭代器协议的对象
协议是一种约定,可迭代对象实现迭代器协议,Python的内置工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。
举个例子理解一下:
1 |
>>> x = [1, 2, 3] |
这里x是一个可迭代对象,可迭代对象和容器一样是一种通俗的叫法,并不是指某种具体的数据类型,list是可迭代对象,dict是可迭代对象,set也是可迭代对象。y和z是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器有一种具体的迭代器类型,比如list_iterator,set_iterator。可迭代对象实现了iter方法,该方法返回一个迭代器对象。
当运行代码:
1 |
x = [1, 2, 3] |
实际执行情况是:
反编译该段代码,你可以看到解释器显示地调用GET_ITER指令,相当于调用iter(x),FOR_ITER指令就是调用next()方法,不断地获取迭代器中的下一个元素,但是你没法直接从指令中看出来,因为他被解释器优化过了。
1 |
>>> import dis |
0x2.2 可迭代对象的判断
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:
1 |
>>> from collections import Iterable |
0x3迭代器
0x3.1迭代器的概念
实现了__iter__
和__next__
方法的对象都称为迭代器。迭代器是一个有状态的对象,在调用next() 的时候返回下一个值,如果容器中没有更多元素了,则抛出StopIteration异常
所以,迭代器就是实现了工厂模式的对象,它在你每次你询问要下一个值的时候给你返回。
0x3.2迭代器的特点
迭代是Python最强大的功能之一,是访问集合元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。 迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
0x3.3迭代器的例子
有很多关于迭代器的例子,比如itertools函数返回的都是迭代器对象。
生成无限序列:
1 |
>>> from itertools import count |
从一个有限序列中生成无限序列:
1 |
>>> from itertools import cycle |
从无限的序列中生成有限序列:
1 |
>>> from itertools import islice |
0x3.4迭代器的创建
把一个类作为一个迭代器使用需要在类中实现两个方法 iter() 与 next() 。__iter__()
:方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__()
方法并通过 StopIteration 异常标识迭代的完成。__next__()
:方法(Python 2 里是 next())会返回下一个迭代器对象。
1 |
from itertools import islice |
Fib既是一个可迭代对象(因为它实现了iter方法),又是一个迭代器(因为实现了next方法)。实例变量prev和curr用户维护迭代器内部的状态。每次调用next()方法的时候做两件事:
为下一次调用next()方法修改状态
为当前这次调用生成返回结果
迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。
0x3.5StopIteration
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。
斐波那契数列范例:
1 |
class MyNumbers: |
0x4生成器
0x4.1生成器的概念以及范例
生成器其实是一种特殊的迭代器,但是不需要像迭代器一样实现iter和next方法,只需要使用关键字yield就可以。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象
斐波那契数列例子:
1 |
def fib(): |
fib就是一个普通的python函数,它特殊的地方在于函数体中没有return关键字,函数的返回值是一个生成器对象。当执行f=fib()返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。
生成器在Python中是一个非常强大的编程结构,可以用更少地中间变量写流式代码,此外,相比其它容器对象它更能节省内存和CPU,当然它可以用更少的代码来实现相似的功能。现在就可以动手重构你的代码了,但凡看到类似:
1 |
def something(): |
都可以用生成器函数来替换:
1 |
def iter_something(): |
0x4.2生成器表达式
生成器表达式是列表推倒式的生成器版本,看起来像列表推导式,但是它返回的是一个生成器对象而不是列表对象。
1 |
>>> a = (x*x for x in range(10)) |
0x5 迭代器的问题与解法(扩展)
0x5.1手动访问迭代器中的元素
问题:
在处理某个可迭代对象中的元素,因为某些原因,不能也不想使用for循环。
解决方案:
使用next()函数。
例子:
1 |
with open('h://flag.txt','r') as f: |
如果是手动使用next(),也可以命令它返回一个结束值,比如说None
示例如下:
1 |
with open('h://flag.txt','r') as f: |
0x5.2委托迭代
问题:
我们构建了一个自定义的容器对象,其内部持有一个列表、元组或其他的可迭代对象。我们想让自己的容器能够完成迭代操作。
解决方案:
一般来说,我们所要做的就是定义一个__iter__()
方法,将迭代请求委托到对象内部持有的容器上。
1 |
class Node(): |
在这个例子中,__iter__
方法只是简单地将迭代请求转发 给对象内部持有 的_childer
属相上。
iter(s)通过调用 s.__iter__()
来简单的返回底层迭代器。
0x5.3用生成器来创建新的迭代模式
问题:
我们向自定义一个迭代模式,使其区别于常见的内建函数(即range(),reversed()等)
解决方法:
可使用生成器函数来定义
1 |
def frange(start,stop,increment): |
0x5.4实现迭代协议
问题:
我们正在构建一个自定义的对象,希望它可以支持迭代操作,但是也希望有一种简单的方式来实现迭代协议。
解决方案:
目前来看,要在对象上实现可迭代功能,最简单的方式就是使用生成器函数。
范例:
实现一个迭代器能够以深度优先的模式遍历树的节点
1 |
class Node(object): |
0x5.5反向迭代
问题:
我们想要反向迭代序列中的元素
解决方案:
可以使用内建的reversed()函数实现反向迭代
1 |
a=[1,2,3,4] |
反向迭代的条件:
只有在待处理的对象有用可确定的大小,或者对象实现了__reversed__()
特殊方法时,才能奏效。
如果这两个条件都无法满足,则必须首先将这个对象转化为列表。
可以在自定义的类上实现__reversed__()
方法,实现反向迭代
例如:
1 |
class Countdown(object): |
0x5.6定义带有额外状态的生成器函数
问题:
我们想要定义一个生成器函数,但是它涉及一些额外的状态,我们希望能以某种形式将这些状态暴露给用户。
解决方案:
如果想让生成器将状态暴露给用户,别忘了可以轻易地将其实现为一个类,然后生成器函数的代码放到__iter__()
方法中。
1 |
from collections import deque |
0x5.7对迭代器做切片操作
问题:
我们相对迭代器产生的数据做切片处理,但是普通的切片操作符在这里不管用。
解决方案:
要对迭代器和生成器做切片操作,itertools.islice()函数是完美的选择。
1 |
import itertools |
函数 islice() 返回一个可以生成指定元素的迭代器,它通过遍
历并丢弃直到切片开始索引位置的所有元素。然后才开始一个个的返回元素,并直到切片结束索引位置。缺点不能重复使用迭代器里面的数据
0x5.8跳过不需要的迭代部分
使用itertools.dropwhile()函数实现
范例:
1 |
from itertools import dropwhile |
0x5.9迭代所有可能的组合或排列
itertools.permutaions()
接受一个元素集合,将其中所有的元素重排列为所有可能的情况,并以元组序列的形式返回itertools.conbiations()
可产生输入序列中所有元素的全部组合形式
1 |
from itertools import permutations,combinations, combinations_with_replacement |
0x5.10以索引-值对的形式迭代序列
内建的enumerate()函数解决
1 |
my_list = ['a', 'b', 'c'] |
zip(a,b)的工作原理是创建出一个迭代器,该迭代器可产生出元组(x,y),这里的x取自序列啊,而y取自序列b。
0x5.11同时迭代多个序列
使用zip()函数来同时迭代多个序列
1 |
a = [1, 2, 3] |
打包字典。变成序列。
1 |
headers = ['name', 'shares', 'price'] |
0x5.12 在不同容器中进行迭代
itetools.chain()可接受一个或多个可迭代对象作为参数,然后它会创建一个迭代器,该迭代器可连续访问你提供的每个可迭代对象中的元素。
1 |
from itertools import chain |
0x5.13扁平化处理嵌套型序列
1 |
from collections import Iterable |
0x5.14合并多个有序序列再迭代
heapq.merge()
heapq.merge 生成器迭代特性意味着它不会立马读取所有序列。这就意味着你可以在非
常长的序列中使用它,而不会有太大的开销
1 |
import heapq |
0x5.15迭代器代替while循环
其实就是用遍历代替while.
途径:iter(functiong, status)能够迭代
常见的IO程序,伪代码
1 |
CHUNKSIZE = 8192 |
实例代码
1 |
import sys |
作者:brother阿张
链接:https://www.jianshu.com/p/083cb153c623
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
参考文章:
完全理解Python迭代对象、迭代器、生成器 https://foofish.net/iterators-vs-generators.html
廖雪峰的官方网站https://www.liaoxuefeng.com/wiki/1016959663602400/1017323698112640
如何更好地理解Python迭代器和生成器? https://www.zhihu.com/question/20829330
pythoncookbook 第4章 生成器与迭代器https://www.jianshu.com/p/083cb153c623
FROM :blog.cfyqy.com | Author:cfyqy
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论