我决定把Python进阶系列改为Python终结者系列,因为你看完一篇文章就会彻底终结你对某个概念的理解。
针对上周训练营同学提出的问题,本文用简洁的语言讲解iterable, iterator和generator的根本特征,帮你终结它们的关系和区别。
一、先上结论
先说结论,再解释:
iterable是可迭代对象,它的唯一特征是有__iter__函数,调用这个函数会返回一个iterator。iterator是迭代器,它的唯一特征是有__next__函数,调用这个函数会返回下一个元素。有些类同时有以上两个函数,所以即是iterable,又是iterator,这是为了方便,不用额外创建iterator类。generator是用yield函数定义的iterator。它必然也有__next__函数。二、几个iterable例子
可以用for循环遍历的对象都是可迭代对象iterable,比如list, range, tuple等:
nums = [9527, 3721, 56, 97] for n in nums: print(n) for n in range(1, 100): print(n) seasons = ('春', '夏','秋','冬') for s in seasons: print(s)
iterable还有很多,它们的唯一必要特征是:
iterable必须有__iter__函数,这个函数的作用就是返回迭代器iterator。
请默默把这句话重复3遍,记住它!
我们来验证一下这个特征,用dir函数看看上面3个iterable有没有__iter__函数:
再说一遍,__iter__函数是iterable的唯一特征。
注意,这些对象并没有__next__函数,因为__next__是iterator的特征。上面3个都不是iterator。
三、设计模式和原理
迭代器是一个通用的设计模式,不同编程语言都有各自的实现。
我们把list,tuple,和range等iterable当做是一个仓库,里面存放着可以被取用的东西。它们的内部结构各不相同。为了能够用for循环遍历仓库中的东西,我们给这些iterable都配上一个仓库管理员,那就是iterator。for只要找仓库管理员就可以了,而不需要知道里面具体如何存放的。这样问题就简单了。__iter__函数是从仓库中获得管理员的函数。一个对象只要有这个函数,就相当于配备了仓库管理员,就是可迭代对象,就是iterable。现在看iterator,它的唯一特征就是有__next__函数,for循环每次调用这个函数获得下一个元素,直到出现StopIteration异常为止。整个过程就是这样的:
通过这个设计模式,for循环不管被迭代的对象是什么牛鬼蛇神(正方形,圆,对话框,圆角正方形等等),它只找管理员:
调用iterable的__iter__函数,获得iterator对象。注意:实际上for是调用Python内置的iter()函数,而这个函数又调用了iterable的__iter__函数。调用iterator的__next__函数,获取下一个元素,直到获得StopIteration为止。注意:实际上for是调用Python内置的next()函数,而这个函数又调用了iterator的__next__函数。我们来验证一下:
从图中可以看出:
a是一个列表调用iter(a)返回的是一个list_iterator对象这个对象有__next__函数到这里你应该对iterator和iterable有了比较好的了解。这里涉及到两个类:
一个对象是仓库,也就是iterable。一个对象是仓管员,也就是iterator。定义自己的Iterator和Iterable
理解了这个原理,我们可以轻松创建一个可迭代对象以及它的迭代器。下面我们来创建一个随机列表:RandList。它的特点是:随机遍历访问一个列表中的内容。
先定义一个iterator:
import random # 这是一个iterator class RandListIterator(): def __init__(self, rand_list): # ***一份列表,防止影响原列表 self.list = rand_list[:] # 每次遍历随机选择一个,并删除已经返回的元素,直到列表为空 def __next__(self): if len(self.list) == 0: raise StopIteration index = random.randint(0, len(self.list)-1) value = self.list[index] del self.list[index] return value
在定义一个iterable,它使用前面定义的iterator:
# 这是一个iterable class RandList(): def __init__(self, alist): self.list = alist def __iter__(self): return RandListIterator(self.list)
现在用for循环来循环一下:
alist = [4, 6, 3, 8, 23, 1] for i in RandList(alist): print(i)
可以随机的访问列表中的内容:
一个类既是iterable,也是iterator
上面的例子,我们定义了两个类。很多时候为了方便,我们会让一个类把两件事情都做了,毕竟只要这个类同时有__iter__和__next__函数就可以了。
我们现在定一个随机数生成器,我把它命名为Randable,它的功能是:随机生成10个1到100之间的随机数。
import random class Randable(): def __init__(self): self.count = 0 def __iter__(self): return self def __next__(self): if self.count == 10: raise StopIteration rand_num = random.randint(1, 100) self.count += 1 return rand_num
这个类同时包含了__iter__和__next__两个函数,自己是仓库,又是仓库管理员。就是为了代码方便点,毕竟确实没必要写到两个类里。
使用上面的Randable类:
for i in Randable(): print(i)
执行结果:
到这里,你对iterable和iterator的理解应该很透彻了吧。对这个概念的理解应该可以终结了!
如果没有终结,请多读两遍,也可以给我留言。
更简单的写法generator
就算第二种写法,用一个类定义生成器和迭代器仍然有点复杂,需要定义类。Geneator是一种更简单的写法,只要一个函数就够了。来改写一下上面的Randable的例子:
def randable(): for _ in range(0, 10): yield random.randint(1, 10)
这样就完事了,现在可以循环了:
for i in randable(): print(i)
WTF??? 这也太简单了吧!
这就是Generator的魅力所在。不过Generator的概念,稍微有点复杂,我们下次再终结。
最后,如果看了这个文章,你对Iterable和Iterator的概念终结了,请在评论区留言告诉我。谢谢。