python的迭代器和生成器

admin 2022年1月6日01:33:52评论51 views字数 10648阅读35分29秒阅读模式

容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)众多概念,有点复杂,整理一下。

python的迭代器和生成器
详情可以看一篇文章:https://foofish.net/iterators-vs-generators.ht

0x1容器(container)

容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in, not in关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特例,并不是所有的元素都放在内存,比如迭代器和生成器对象)
在Python中,常见的容器对象有:

1
2
3
4
5
list, deque, ....
set, frozensets, ....
dict, defaultdict, OrderedDict, Counter, ....
tuple, namedtuple, …
str

容器比较容易理解,因为你就可以把它看作是一个盒子、一栋房子、一个柜子,里面可以塞任何东西。从技术角度来说,当它可以用来询问某个元素是否包含在其中时,那么这个对象就可以认为是一个容器,比如 list,set,tuples都是容器对象:

1
2
3
4
5
>>> 4 not in [1, 2, 3]
True
>>> 1 in [1, 2, 3]
True
>>>

尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象赋予了容器这种能力,当然并不是所有的容器都是可迭代的,比如:Bloom filter,虽然Bloom filter可以用来检测某个元素是否包含在容器中,但是并不能从容器中获取其中的每一个值,因为Bloom filter压根就没把元素存储在容器中,而是通过一个散列函数映射成一个值保存在数组中。

0x2可迭代对象(iterable)

0x2.1可迭代对象的理解

迭代器协议是指:对象需要提供next方法,它要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代
可迭代对象就是:实现了迭代器协议的对象
协议是一种约定,可迭代对象实现迭代器协议,Python的内置工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。
举个例子理解一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> x = [1, 2, 3]
>>> y = iter(x)
>>> z = iter(x)
>>> next(y)
1
>>> next(y)
2
>>> next(z)
1
>>> type(x)
<class 'list'>
>>> type(y)
<class 'list_iterator'>

这里x是一个可迭代对象,可迭代对象和容器一样是一种通俗的叫法,并不是指某种具体的数据类型,list是可迭代对象,dict是可迭代对象,set也是可迭代对象。y和z是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器有一种具体的迭代器类型,比如list_iterator,set_iterator。可迭代对象实现了iter方法,该方法返回一个迭代器对象。
当运行代码:

1
2
3
x = [1, 2, 3]
for elem in x:
...

实际执行情况是:
python的迭代器和生成器
反编译该段代码,你可以看到解释器显示地调用GET_ITER指令,相当于调用iter(x),FOR_ITER指令就是调用next()方法,不断地获取迭代器中的下一个元素,但是你没法直接从指令中看出来,因为他被解释器优化过了。

1
2
3
4
5
6
7
8
9
10
11
12
>>> import dis
>>> x=[1,2,3]
>>> dis.dis('for _ in x:pass')
1 0 SETUP_LOOP 12 (to 14)
2 LOAD_NAME 0 (x)
4 GET_ITER
>> 6 FOR_ITER 4 (to 12)
8 STORE_NAME 1 (_)
10 JUMP_ABSOLUTE 6
>> 12 POP_BLOCK
>> 14 LOAD_CONST 0 (None)
16 RETURN_VALUE

0x2.2 可迭代对象的判断
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:

1
2
3
4
5
6
7
8
9
10
11
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

0x3迭代器

0x3.1迭代器的概念
实现了__iter____next__方法的对象都称为迭代器。迭代器是一个有状态的对象,在调用next() 的时候返回下一个值,如果容器中没有更多元素了,则抛出StopIteration异常
所以,迭代器就是实现了工厂模式的对象,它在你每次你询问要下一个值的时候给你返回。
0x3.2迭代器的特点
迭代是Python最强大的功能之一,是访问集合元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。 迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
0x3.3迭代器的例子
有很多关于迭代器的例子,比如itertools函数返回的都是迭代器对象。
生成无限序列:

1
2
3
4
5
6
>>> from itertools import count
>>> counter = count(start=13)
>>> next(counter)
13
>>> next(counter)
14

从一个有限序列中生成无限序列:

1
2
3
4
5
6
7
8
9
10
>>> from itertools import cycle
>>> colors = cycle(['red', 'white', 'blue'])
>>> next(colors)
'red'
>>> next(colors)
'white'
>>> next(colors)
'blue'
>>> next(colors)
'red'

从无限的序列中生成有限序列:

1
2
3
4
5
6
7
8
9
>>> from itertools import islice
>>> colors = cycle(['red', 'white', 'blue']) # infinite
>>> limited = islice(colors, 0, 4) # finite
>>> for x in limited:
... print(x)
red
white
blue
red

0x3.4迭代器的创建
把一个类作为一个迭代器使用需要在类中实现两个方法 iter() 与 next() 。
__iter__() :方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__() 方法并通过 StopIteration 异常标识迭代的完成。
__next__() :方法(Python 2 里是 next())会返回下一个迭代器对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from itertools import islice
class Fib:
def __init__(self):
self.prev = 0
self.curr = 1

def __iter__(self):
return self

def __next__(self):
value = self.curr
self.curr += self.prev
self.prev = value
return value
f=Fib()
print(list(islice(f,0,10)))


output:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
[Finished in 0.2s]

Fib既是一个可迭代对象(因为它实现了iter方法),又是一个迭代器(因为实现了next方法)。实例变量prev和curr用户维护迭代器内部的状态。每次调用next()方法的时候做两件事:

为下一次调用next()方法修改状态
为当前这次调用生成返回结果

迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。
0x3.5StopIteration
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。
斐波那契数列范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyNumbers:
def __iter__(self):
self.a = 1
return self

def __next__(self):
if self.a <= 21:
x = self.a
self.a += 1
return x
else:
raise StopIteration

myclass = MyNumbers()
myiter = iter(myclass)
for x in myiter:
print(x)

0x4生成器

0x4.1生成器的概念以及范例
生成器其实是一种特殊的迭代器,但是不需要像迭代器一样实现iternext方法,只需要使用关键字yield就可以。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

调用一个生成器函数,返回的是一个迭代器对象
斐波那契数列例子:

1
2
3
4
5
6
7
8
9
def fib():
prev, curr = 0, 1
while True:
yield curr
prev, curr = curr, curr + prev

>>> f = fib()
>>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

fib就是一个普通的python函数,它特殊的地方在于函数体中没有return关键字,函数的返回值是一个生成器对象。当执行f=fib()返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。
生成器在Python中是一个非常强大的编程结构,可以用更少地中间变量写流式代码,此外,相比其它容器对象它更能节省内存和CPU,当然它可以用更少的代码来实现相似的功能。现在就可以动手重构你的代码了,但凡看到类似:

1
2
3
4
5
def something():
result = []
for ... in ...:
result.append(x)
return result

都可以用生成器函数来替换:

1
2
3
def iter_something():
for ... in ...:
yield x

0x4.2生成器表达式
生成器表达式是列表推倒式的生成器版本,看起来像列表推导式,但是它返回的是一个生成器对象而不是列表对象。

1
2
3
4
5
>>> a = (x*x for x in range(10))
>>> a
<generator object <genexpr> at 0x401f08>
>>> sum(a)
285

0x5 迭代器的问题与解法(扩展)

0x5.1手动访问迭代器中的元素

问题:
在处理某个可迭代对象中的元素,因为某些原因,不能也不想使用for循环。
解决方案:
使用next()函数。
例子:

1
2
3
4
5
6
7
with open('h://flag.txt','r') as f:
try:
while True:
line=next(f)
print(line,end='')
except StopIteration:
pass

如果是手动使用next(),也可以命令它返回一个结束值,比如说None
示例如下:

1
2
3
4
5
6
with open('h://flag.txt','r') as f:
while True:
line=next(f,None)
if line is None:
break
print(line,end='')

0x5.2委托迭代

问题:
我们构建了一个自定义的容器对象,其内部持有一个列表、元组或其他的可迭代对象。我们想让自己的容器能够完成迭代操作。
解决方案:
一般来说,我们所要做的就是定义一个__iter__()方法,将迭代请求委托到对象内部持有的容器上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Node():
def __init__(self,value):
self._value=value
self._children=[]
def __repr__(self):#这个函数就是在打印类的时候,控制类输出的字符串相当于java中的toString()
return 'Node({})'.format(self._value)
def add_child(self,node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
if __name__=='__main__':
root=Node(0)
child1=Node(1)
child2=Node(2)
root.add_child(child1)
root.add_child(child2)
for ch in root:
print(ch)

output:
Node(1)
Node(2)
[Finished in 0.2s]

在这个例子中,__iter__方法只是简单地将迭代请求转发 给对象内部持有 的_childer属相上。
iter(s)通过调用 s.__iter__()来简单的返回底层迭代器。

0x5.3用生成器来创建新的迭代模式

问题:
我们向自定义一个迭代模式,使其区别于常见的内建函数(即range(),reversed()等)
解决方法:
可使用生成器函数来定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def frange(start,stop,increment):
x=start
while x<stop:
yield x
x+=increment
for n in frange(0,2,0.4):
print(n)


output:
0
0.4
0.8
1.2000000000000002
1.6
#主要还是因浮点数在计算机中实际是以二进制保存的,有些数不精确

0x5.4实现迭代协议
问题:
我们正在构建一个自定义的对象,希望它可以支持迭代操作,但是也希望有一种简单的方式来实现迭代协议。
解决方案:
目前来看,要在对象上实现可迭代功能,最简单的方式就是使用生成器函数。
范例:
实现一个迭代器能够以深度优先的模式遍历树的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Node(object):
def __init__(self, value):
self._value = value
self._children =[]

def __repr__(self):
return "Node{!r}".format(self._value)

def __iter__(self):
return iter(self._children)

def add_child(self, node):
return self._children.append(node)

def depth_first(self):

yield self
for c in self:
#下面的代码相当于yield from c.depth_first()
for items in c.depth_first():
yield items

if __name__ == '__main__':

root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child2.add_child(Node(4))
child1.add_child(Node(5))
for ch in root.depth_first():
print(ch)

0x5.5反向迭代

问题:
我们想要反向迭代序列中的元素
解决方案:
可以使用内建的reversed()函数实现反向迭代

1
2
3
a=[1,2,3,4]
for x in reversed(a):
print(x)

反向迭代的条件:
只有在待处理的对象有用可确定的大小,或者对象实现了__reversed__()特殊方法时,才能奏效。
如果这两个条件都无法满足,则必须首先将这个对象转化为列表。

可以在自定义的类上实现__reversed__()方法,实现反向迭代
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Countdown(object):
def __init__(self, start):
self.start = start

def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1

def __reversed__(self):
n = 1
while n <= self.start:
yield n
n += 1


if __name__ == '__main__':
for rr in reversed(Countdown(30)):
print(rr)
for rr in Countdown(30):
print(rr)

0x5.6定义带有额外状态的生成器函数

问题:
我们想要定义一个生成器函数,但是它涉及一些额外的状态,我们希望能以某种形式将这些状态暴露给用户。
解决方案:
如果想让生成器将状态暴露给用户,别忘了可以轻易地将其实现为一个类,然后生成器函数的代码放到__iter__()方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from collections import deque

class LineHistory:
def __init__(self, lines, hislen=3):
self.lines = lines
self.history = deque(maxlen=hislen)

def __iter__(self):
for lineno, line in enumerate(self.lines, 1):
self.history.append((lineno, line))
yield line

def clear(self):
self.history.clear()
with open('h://flag.txt','r') as f:
lines=LineHistory(f)
for line in lines:
if 'hello' in line:
for lineno,hline in lines.history:
print('{}:{}'.format(lineno,hline,end=''))

0x5.7对迭代器做切片操作

问题:
我们相对迭代器产生的数据做切片处理,但是普通的切片操作符在这里不管用。
解决方案:
要对迭代器和生成器做切片操作,itertools.islice()函数是完美的选择。

1
2
3
4
5
6
7
8
9
import itertools
def count(n):
while True:
yield n
n += 1
c = count(0)
# c[10:20] >>>TypeError: 'generator' object has no attribute '__getitem__'
for items in itertools.islice(c, 10, 21):
print(items)

函数 islice() 返回一个可以生成指定元素的迭代器,它通过遍
历并丢弃直到切片开始索引位置的所有元素。然后才开始一个个的返回元素,并直到切片结束索引位置。缺点不能重复使用迭代器里面的数据

0x5.8跳过不需要的迭代部分

使用itertools.dropwhile()函数实现
范例:

1
2
3
4
from itertools import dropwhile
with open('h://flag.txt','r') as f:
for line in dropwhile(lambda line: line.startswith("h"), f):
print(line,end='')

0x5.9迭代所有可能的组合或排列

itertools.permutaions() 接受一个元素集合,将其中所有的元素重排列为所有可能的情况,并以元组序列的形式返回
itertools.conbiations()可产生输入序列中所有元素的全部组合形式

1
2
3
4
5
6
7
8
9
10
from itertools import permutations,combinations, combinations_with_replacement
items = ['a', 'b', 'c']
for c in permutations(items): # 排列A33
print(c)
for c in permutations(items, 2): # 排列A33
print(c)
for c in combinations(items, 3): # 组合 C23
print(c)
for c in combinations_with_replacement(items, 3): # 同一元素重复使用 3*3*3
print(c)

0x5.10以索引-值对的形式迭代序列

内建的enumerate()函数解决

1
2
3
my_list = ['a', 'b', 'c']
for idx, val in enumerate(my_list, 1):
print(idx, val)

zip(a,b)的工作原理是创建出一个迭代器,该迭代器可产生出元组(x,y),这里的x取自序列啊,而y取自序列b。

0x5.11同时迭代多个序列

使用zip()函数来同时迭代多个序列

1
2
3
4
5
6
7
8
9
10
11
a = [1, 2, 3]
b = ['w', 'x', 'y', 'z']
for i in zip(a,b):
print(i)


output:
(1, 'w')
(2, 'x')
(3, 'y')
[Finished in 0.2s]

打包字典。变成序列。

1
2
3
4
5
headers = ['name', 'shares', 'price']
values = ['ACME', 100, 490.1]
s = dict(zip(headers,values))
print(s)
list(zip(headers, values))

0x5.12 在不同容器中进行迭代

itetools.chain()可接受一个或多个可迭代对象作为参数,然后它会创建一个迭代器,该迭代器可连续访问你提供的每个可迭代对象中的元素。

1
2
3
4
5
from itertools import chain
a = (1, 2, 3, 4)
b = ['x', 'y', 'z']
for x in chain(a, b):
print(x)

0x5.13扁平化处理嵌套型序列

1
2
3
4
5
6
7
8
9
10
11
12
from collections import Iterable
def flatten(items, ignore_types=(str, bytes)):
for x in items:
#isinstance(x, Iterable) 判断是否可以迭代 ,可以则继续递归
#not isinstance(x, ignore_types),排除字符串,字节,这两者也可以迭代
if isinstance(x, Iterable) and not isinstance(x, ignore_types):
yield from flatten(x)
else:
yield x
items1 = [1, 2, [3, 4, [5, 6], 7], 8]
for x in flatten(items1):
print(x)

0x5.14合并多个有序序列再迭代

heapq.merge()
heapq.merge 生成器迭代特性意味着它不会立马读取所有序列。这就意味着你可以在非
常长的序列中使用它,而不会有太大的开销

1
2
3
4
import heapq
a = [1, 4, 7, 10]
b = [2, 5, 6, 11]
l = [x for x in heapq.merge(a, b)]

0x5.15迭代器代替while循环

其实就是用遍历代替while.
途径:iter(functiong, status)能够迭代
常见的IO程序,伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CHUNKSIZE = 8192
def reader(s):
while True:
data = s.recv(CHUNKSIZE)
if data == b'':
break
process_data(data)

f = open("views.py", "r")
reader(f)

#用iter()循环代替
def reader2(s):
for chunk in iter(lambda : s.recv(CHUNKSIZE),b""):
pass
#process_data(data)

实例代码

1
2
3
4
import sys
f = open("views.py","r")
for chunk in iter(lambda: f.read(10), ""):
n = sys.stdout.write(chunk)

作者: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

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月6日01:33:52
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   python的迭代器和生成器http://cn-sec.com/archives/721853.html

发表评论

匿名网友 填写信息