简单来说可迭代对象包含迭代器,迭代器包含生成器。
可迭代对象和迭代器
迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。
在python中,可迭代的表象是对list、tuple、dict、set、str、range对象等类型的数据使用for循环依次从其中拿到数据进行使用,这个过程称做遍历,也叫迭代。
代码角度看是内部含有__iter__
方法的对象就是可迭代对象。__iter__
方法的作用就是返回一个迭代器对象。
for循环过程中具体的实现方式是什么
x = [1, 2, 3]
for i in x:
print(i)
- 调用可迭代对象的
__iter__
方法返回一个迭代器对象(iterator) - 不断调用迭代器的
__next__
方法返回元素 - 知道迭代完后,处理StopIteration异常
迭代器是实现了__next__
方法的对象。
所以定义可迭代对象必须实现__iter__
方法,定义迭代器必须实现__iter__
和__next__
方法。
判断一个对象是不是可迭代对象
In [50]: from collections import Iterable
In [51]: isinstance([], Iterable)
Out[51]: True
In [52]: isinstance({}, Iterable)
Out[52]: True
In [53]: isinstance('abc', Iterable)
Out[53]: True
In [55]: isinstance(100, Iterable)
Out[55]: False
__iter__
和__next__
具体实现
__iter__
有两种写法:
写法一:用户可迭代对象,返回该可迭代对象的迭代器实例
写法二:用户迭代器的写法,返回self(迭代器本身),表示自身就是迭代器。
__next__
方法,返回迭代的每一步,实现该方法最后注意超出边界要抛出StopIteration异常。
class Mylist: #定义可迭代对象类
def __init__(self, data):
self.data = data #上边界
def __iter__(self):
return MylistIterator(self.data) #返回迭代器实例
class MylistIterator: #定义迭代器类
def __init__(self, data):
self.now = 0 #当前迭代值,初始为0
self.data = data #迭代器上边界
def __iter__(self):
return self #返回迭代器本身
def __next__(self): #迭代器必须实现next方法
while self.now < self.data:
self.now += 1
return self.now - 1
raise StopIteration #超出上边界,抛出异常
mylist = Mylist(5) #得到一个可迭代对象
print(type(mylist)) #返回该对象类型
mylist_iter = iter(mylist) #得到一个迭代器实例
print(type(mylist_iter))
# for i in mylist_iter:
# print(i)
next(mylist_iter)
返回值
<class '__main__.Mylist'>
<class '__main__.MylistIterator'>
0
迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。直到无元素可调用,返回StopIteration异常。
生成器
生成器是一种特殊的迭代器,生成器自动实现了“迭代器协议”(即__iter__
和__next__
方法),不需要手动实现两个方法,只需要一个yield关键字。
生成器在迭代过程中可以改变当前迭代值,而修改普通迭代器的当前迭代值会发生异常,影响程序的执行。
为什么要使用生成器:节省空间
通过列表生成式,我们可以直接创建一个列表,但是受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前几个元素,那么后面绝大多数元素占用的空间就白费了。
所以,如果列表可以按照某种算法推算出来,那我们是否可以在循环过程中不断推算出后面的元素呢?这样就不必创建完整的list,从而节省了大量的空间。在python中,这种一边循环一边计算的机制,称为生成器。
计算斐波那契的生成器
def fibon(n):
a = b = 1
for i in range(n):
yield a
a, b = b, a + b
fibon(10)
<generator object fibon at 0x00000280C97E22C8>
使用循环打印出来
for i in fibon(10):
print(i)
如果用函数来实现斐波那契数列
def fibon(n):
result = []
a = b = 1
for i in range(n):
result.append(a)
a, b = b, a + b
return result
fibon(10)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
生成器特殊的地方在于函数体中没有return
关键字,函数的返回值是一个生成器对象。当执行f=fib()
返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。
具有yield关键字的函数都是生成器,yield可以理解为return,返回后面的值给调用者。不同的是return返回后,函数会释放,而生成器则不会,在直接调用next方法或用for语句进行下一次迭代时,生成器会从yield下一句开始执行,直到遇到下一个yield。
生成器还有一个send方法,可以往生成器里的变量传值
def fibon(n):
a = b = 1
for i in range(n):
c = yield a
print(c)
a, b = b, a + b
a=fibon(10)
print(next(a))
print(next(a))
print(next(a))
print(a.send(10))
print(next(a))
print(next(a))
print(next(a))
返回值
1
None
1
None
2
10 #这里的None被换成了10
3
None
5
None
8
None
13
要实现send方法有效果,必须得把yield表达式赋值一个一个变量,然后再打印。不然完全没有反应。可能还是暂时没弄懂。
生成器表达式
生成器表达式和列表推导式很像,就是把方括号换成小括号
[i for i in range(10)] #列表推导式或解析式
返回
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
生成器表达式
(i for i in range(10))
返回
<generator object <genexpr> at 0x000002303BC9A0C8>
hon
[i for i in range(10)] #列表推导式或解析式
返回
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
生成器表达式
```python
(i for i in range(10))
返回
<generator object <genexpr> at 0x000002303BC9A0C8>
折腾了好久总觉得差点意思,就先把这个当做一个开头,后面再补充。