可迭代对象:
表面来看,只要可以用 for...in...进行遍历的对象就是可迭代对象
语法层面,如果一个对象实现了__iter__方法,那么这个对象就是可迭代对象
判断一个对象是否是可迭代对象:
可以使用 isinstance() 判断一个对象是否是 Iterable 对象
from collections import Iterable
In [5]: isinstance("abc",Iterable)
Out[5]: True
In [6]: isinstance(100,Iterable)
Out[6]: False
可迭代对象的本质:
只要实现了__iter__方法:
例子:实现可迭代对象:
下面我们写两个类:一个类没有__iter__方法,一个类有__iter__方法,在用instance()方法来判断
from collections import Iterable
class Test_1(object):
def __init__(self):
self.mylist_1 = list()
def add(self,item):
self.mylist_1.append(item)
def __iter__(self):
pass
class Test_2(object):
def __init__(self):
self.mylist_2 = list()
def add(self,item):
self.mylist_2.append(item)
def main():
t1 = Test_1();
t2 = Test_2();
if isinstance(t1, Iterable):
print("类中存在__iter__()方法,这个类创建的对象为可迭代对象")
else:
print("要验证的结果失败")
if not isinstance(t2, Iterable):
print("这个类创建的对象为不可迭代对象")
else:
print("要验证的结果失败")
if __name__ == "__main__":
main()
测试结果如下:
iter()函数与next()函数
list、tuple等都是可迭代对象,我们可以通过iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据。iter()函数实际上就是调用了可迭代对象的__iter__
方法。
迭代器:
说白了:一个实现了__iter__
方法和__next__
方法的对象,就是迭代器。
代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的__next__
方法。所以,我们要想构造一个迭代器,就要实现它的__next__
方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__
方法,而__iter__
方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__
方法返回自身即可。
判断一个对象是否为迭代器:
from collections import Iterator
迭代器的应用场景:
我们发现迭代器最核心的功能就是可以通过next()函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的数据集合,也就是说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间。
for...in...循环的本质
for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。
接收可迭代对象:
并不是只有for循环能接收可迭代对象
除了for循环能接收可迭代对象,list、tuple等也能接收。
例子:用迭代器的方式生成 斐波那契数列
""""用迭代器生成 斐波那契数列
初始化数列的 前两个初始值 为 0 1
后面的数字为该数字的前两个数字之和
"""
class FibIterator(object):
def __init__(self,n):
self.a = 0
self.b = 1
self.n = n # 用来保存要输出的多少位
self.current = 0 # 用来记录遍历当前的是多少位
def __iter__(self):
# 需要创建迭代器__iter__需要返回一个迭代器,返回自己就行了
return self
def __next__(self):
if self.current<self.n:
num = self.a
self.a, self.b = self.b, num+self.b
self.current += 1
return num
else:
raise StopIteration
def main():
fib = FibIterator(10)
for i in fib:
print(i,end=" ")
print("")
# List tuple等也能够接收迭代器,由于next已经迭代到最后面了,所以这个输出位空
li = list(fib)
print(li)
fib.current = 0
fib.a = 0
fib.b = 1
li = list(fib)
print(li)
if __name__ == "__main__":
main()
输出结果:
生成器:
试想这样一个情况:我们要用到一打串的数字,我们用列表存储起来,但是我们只是需要用到其中的一部分值,这样就会浪费很大的空间
解决办法:生成器
如果列表元素可以按照某种算法推算出来,那我们是可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。
说白了:就是生成器存储的不是某些东西,而存储的是 某种东西的生产工具,等需要的时候生产。
创建生成器:
只要把一个列表生成式的[]
改成()
,就创建了一个generator
In [1]: li = [x*2 for x in range(5)]
In [2]: li
Out[2]: [0, 2, 4, 6, 8]
In [3]: ge = (x*2 for x in range(5))
In [4]: ge
Out[4]: <generator object <genexpr> at 0x7f391c961e60>
在调用ge.next()是出错原因:
python2:使用ge.next()
python3:使用next(ge)
生成器也可以使用for来遍历:
创建生成器的第二方法:yield
先看一个例子:
def fib(n):
a, b = 0, 1
current = 0
while current<n:
yield a
a, b = b,a+b
current += 1
return "down"
f = fib(10)
print(next(f))
print(next(f))
print(next(f))
print("--------------------------")
for i in f:
print(i,end=" ")
print("")
输出结果:
yield关键字有两点作用:
- 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
- 将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用
generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
generator的工作原理,它是在for
循环的过程中不断计算出下一个元素,并在适当的条件结束for
循环。
对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for
循环随之结束。
使用send唤醒
我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。
看这个例子:
def use_send():
for i in range(5):
temp = yield i
print(temp)
t = use_send()
print(next(t))
print(t.send(hahah))
输出结果: