python中的迭代器和生成器
什么是迭代
迭代是可以通过遍历的方式依次把某个对象中的元素取出的方法,在python中,迭代是通过使用for….in….语句完成的
可迭代对象
可以被直接作用于for语句的对象都可以被称为可迭代对象(iterable)。而可以直接作用于for语句的数据类型有以下两种:
- 集合数据类型:str,list,tuple,dict,set…..
- 生成器(generator),包括生成器和带yield的生成器函数
对于某个数据是否属于某种数据类型,可以用isinstance函数来判断
from collections import Iterable
print(isinstance(123, Iterable)) # int类型,输出:False
print(isinstance("123", Iterable)) # str类型,输出:True
print(isinstance([1, 2, 3], Iterable)) # list类型,输出:True
print(isinstance((1, 2, 3), Iterable)) # tuple类型,输出:True
print(isinstance({"a": 1, "b": 2, "c": 3}, Iterable)) # dict类型,输出:True
print(isinstance({1, 2, 3}, Iterable)) # set类型,输出:True
迭代器(iterator)
在python中,可以被next函数调用并不断返回下一个值的对象被称为迭代器(iterator),我们也可以通过isinstance函数对其进行判断
from collections import Iterator
print(isinstance("123", Iterator)) # str类型,输出:False
print(isinstance([1, 2, 3], Iterator)) # list类型,输出:False
print(isinstance((1, 2, 3), Iterator)) # tuple类型,输出:False
print(isinstance({"a": 1, "b": 2, "c": 3}, Iterator)) # dict类型,输出:False
print(isinstance({1, 2, 3}, Iterator)) # set类型,输出:False
从上面可以看出,可迭代对象可以不是迭代器,如果我们想把他们转换为迭代器,可以使用iter函数
from collections import Iterator
print(isinstance(iter("123"), Iterator)) # str类型,输出:True
print(isinstance(iter([1, 2, 3]), Iterator)) # list类型,输出:True
print(isinstance(iter((1, 2, 3)), Iterator)) # tuple类型,输出:True
print(isinstance(iter({"a": 1, "b": 2, "c": 3}), Iterator)) # dict类型,输出:True
print(isinstance(iter({1, 2, 3}), Iterator)) # set类型,输出:True
从上面可以看出,是可迭代对象的,不一定是迭代器,是迭代器的,一定是可迭代对象
from collections import Iterator
print(isinstance(iter(123), Iterator)) # TypeError: 'int' object is not iterable
如果是非可迭代对象之间转换为迭代器,可能会报错
迭代器中有两个基本方法:iter(),next()。使用iter函数创建一个迭代器后,就可以通过next函数获取迭代器的下一个值,如果通过next()不断调用并返回下一个值,那么等到最后没有下一个值了,就会抛出异常:StopIteration
a = [1, 2, 3]
iter_a= iter(a) # 创建迭代器对象
print(next(iter_a)) # 输出:1
print(next(iter_a)) # 输出:2
print(next(iter_a)) # 输出:3
print(next(iter_a)) # iter_a没有下一个值了,报错:StopIteration
在python中,for语句本质就是不断调度next函数实现的
a = [1, 2, 3]
for i in a:
print(i)
iter_a = iter(a)
while True:
try:
print(next(iter_a))
except StopIteration:
break
如同这样使用,两者之间其实没有区别
for语句可以循环可迭代对象,迭代器和生成器,所以这里我们可以用for语句循环类对象。但是我们需要在类中写两个魔法方法,iter__和__next,如果没有写,则会报错
class a:
def __init__(self):
pass
b = a()
for i in b: # TypeError: 'a' object is not iterable
print(b)
写了则是这样的
class a:
def __init__(self):
pass
def __iter__(self):
return self # 此次返回当前实例,作为迭代器
def __next__(self):
pass
b = a()
for i in b:
print(b) # <__main__.a object at 0x000002958BBD1FD0>
这个时候,我们或许可以用其来实现一个小玩意儿:斐波那契数列
class a:
def __init__(self, n):
self.n = n
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
if self.n > 0:
self.n -= 1
self.a, self.b = self.b, self.a + self.b
return self.a
else:
raise StopIteration
b = a(11)
for i in b:
print(i) # 输出:1 1 2 3 5 8 13 21 34 55 89
生成器
在python中,生成器(generator)也是用在迭代操作中,其本质上可以理解为一个特殊的迭代器,生成器具有和迭代器一样的特性。但是它们在实现方式上不一样。我们可以通过两种方式创建生成器:生成器表达式,生成器函数。
- 生成器表达式
生成器表达式和列表推导式差不多,我们只需要包列表推导式的[]改为(),这样就是一个生成器表达式了。需要注意的是,列表推导式返回的是一个列表对象,而生成器表达式返回的是一个生成器对象,因此我们可以通过生成器表达式来创建一个生成器。
a = [i for i in range(5)]
print(type(a)) # <class 'list'>
print(a) # [0, 1, 2, 3, 4]
print(next(a)) # TypeError: 'list' object is not an iterator
b = (i for i in range(5))
print(type(b)) # <class 'generator'>
print(b) # <generator object <genexpr> at 0x0000021781CCEE40>
print(next(b)) # 0
- 生成器函数
在Python中,普通函数一般通过 return 来返回一个值,当我们使用关键字 yield 来返回值,那么这个带有 yield 的函数就变成了生成器函数。
def demo1(n):
return n
def demo2(n):
yield n
a = demo1(100)
print(type(a)) # <class 'int'>
print(next(a)) # TypeError: 'int' object is not an iterator
print(next(a))
b = demo2(100)
print(type(b)) # <class 'generator'>
print(next(b)) # 100
print(next(b)) # StopIteration
生成器函数和普通函数在执行流程上是有点区别的。对于普通函数,按顺序执行时遇到 return 或最后一行函数语句就会返回;对于有 yield的生成器函数,每次调用 next() 方法遇到 yield 语句才返回,如果再次调用 next() 方法,那么就会从上次返回的 yield 语句位置继续执行,请看下面的例子:
def demo3():
print("aaa")
yield 1
print("bbb")
yield 2
print("ccc")
yield 3
g = demo3()
next(g) # 输出:aaa
next(g) # 输出:bbb
next(g) # 输出:ccc
next(g) # 没有下一个值了,报错:StopIteration
生成器是一个特殊的迭代器,它使用起来更简单,代码更简洁。如果使用其实现斐波那契数列,则是这样的:
def a(n):
b = 0
c = 1
while True:
if n > 0:
n -= 1
b, c = c, b + c
yield b
for i in a(11):
print(i) # 输出:1 1 2 3 5 8 13 21 34 55 89
生成器还可以用于处理海量数据的场景,比如读取大文件时,如果直接一次性读取可能会导致内存溢出,这个时候我们就可以借助 yield 生成器来灵活控制读取,防止内存占用过大。