python迭代器与生成器

关系图

迭代器

简而言之:带有__iter__()方法的类的实例叫做可迭代对象,因为可以直接使用内置函数 iter(对象),使之变成迭代器,所以它是可迭代的;内置函数 iter()使用时是使用了对象内部的__iter__() 方法, 而对象内部的的__iter__()方法是返回一个迭代器对象(如果自己已经是迭代器,就返回本身)。

带有__next__()方法的类的实例叫做迭代器(生成器也是迭代器),因为它可以直接使用内置函数 next(对象) ,不断迭代来获取容器对象中的一个个元素。next(对象)方法的作用是直接调用对象中的__next__()方法,而对象中的__next__()方法的逻辑实现是返回当前调用的结果并记录每次的调用,以便下一次的调用。当元素用尽时将会引发StopIteration异常。

说起来比较绕,其实相当简单。

迭代器一定可迭代

可迭代对象不一定是迭代器,比如列表,如果 next(列表),会报错(TypeError: ‘list’ object is not an iterator)

既然如此,那么list类中一定没有写__next__()方法,__iter__()返回的也一定不是自己个儿,来看下。

1
2
3
4
5
6
a = [1,2,3,4]

b = iter(a)

print(repr(b))
print(type(b))

他返回的是 list_iterator对象,这是一个迭代器

手写可迭代对象和迭代器

写好类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Iterator:
def __init__(self,con):
self.con = con
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index == len(self.con):
raise StopIteration
else:
a = self.index
self.index += 1
return self.con[a]

class IterableContainer:
def __init__(self):
self.con = []
def __iter__(self):
return Iterator(self.con)
def __str__(self):
return str(self.con)
def append(self,data):
self.con.append(data)
#。。。。。。

具体实例调用,我们就会明白了python的机制:

1
2
3
4
5
6
if __name__ == "__main__":
a = IterableContainer()
for i in range(5):
a.append(i)
print(a)
print(iter(a))

1
2
3
4
5
if __name__ == "__main__":
a = IterableContainer()
for i in range(5):
a.append(i)
print(next(a))

1
2
3
4
5
6
7
8
9
10
11
if __name__ == "__main__":
a = IterableContainer()
for i in range(5):
a.append(i)
b = iter(a)
print("b是啥?",type(b))
try:
for i in range(10):
print(next(b))
except:
print("完成")

使用迭代器的优点

迭代器—不必知道序列底层是怎么实现的,就可以利用迭代器来访问一个序列。

任何容器类,都必须有某种方式可以插入元素并将它们再次取回。毕竟,持有事物是容器的最基本的工作。对于List,add()是插入元素的方法之一,而get()是取出元素的方法之一。

迭代器(也是一种设计模式)的概念可以帮助我们达到一种目的。什么目的呢?能够将遍历序列的操作和序列底层相分离

迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不需要知道该序列底层的结构。此外,迭代器通常被称为轻量级对象:创建它的代价小。因此,经常可以见到对迭代器有一些奇怪的限制;例如,java的Iterator只能单向移动,这个Iterator只能用来:
—能够将遍历序列的操作和序列底层相分离。也正因为如此,我们有时会说:迭代器统一了对容器的访问方式。

引自 ——》迭代器的使用带来什么好处

注意迭代器只能一个方向地遍历下去,统一了对容器的访问方式。

生成器

Generator 是一个用于创建迭代器的简单而强大的工具。 它们的写法类似标准的函数,但当它们要返回数据时会使用 yield 语句。 每次对生成器调用 next() 时,它会从上次离开位置恢复执行(它会记住上次执行语句时的所有数据值)。 显示如何非常容易地创建生成器的示例如下:

1
2
3
4
5
6
7
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]

if __name__ == "__main__":
for char in reverse('golf'):
print(char)

可以用生成器来完成的操作同样可以用上面所描述的基于类的迭代器来完成。 但生成器的写法更为紧凑,因为它会自动创建 __iter__()__next__() 方法。

另一个关键特性在于局部变量和执行状态会在每次调用之间自动保存。 这使得该函数相比使用 self.indexself.data 这种实例变量的方式更易编写且更为清晰。

除了会自动创建方法和保存程序状态,当生成器终结时,它们还会自动引发 StopIteration。 这些特性结合在一起,使得创建迭代器能与编写常规函数一样容易。

简而言之,使用yield返回数据的函数,是生成器,函数名所绑定的对象是生成器,生成器也是迭代器,所以他只能使用一次,若想重新使用,就要重新创建。

斐波那切数列使用生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 菲波那切数列
def Fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return '已经到到底儿了'

f=Fib(10) # 生成器
while True:
try:
x=next(f)
print("f:",x)
except StopIteration as e:
print("生成器最后返回值是:",e.value)
break

生成器表达式

某些简单的生成器可以写成简洁的表达式代码,所用语法类似列表推导式,将外层为圆括号而非方括号。 这种表达式被设计用于生成器将立即被外层函数所使用的情况。 生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。

列表推导式是一次一下生成个列表,执行过程中一直在;

生成器是每次给出一个值,占用空间较少

sum用法: sum(可迭代对象)

1
print(sum(i*i for i in range(10)))


python迭代器与生成器
https://blog.wangxk.cc/2019/09/15/python迭代器与生成器/
作者
Mike
发布于
2019年9月15日
许可协议