Python小知识:迭代器 vs 可迭代对象 vs 生成器

Posted 朝阳区靓仔_James

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python小知识:迭代器 vs 可迭代对象 vs 生成器相关的知识,希望对你有一定的参考价值。

先上结论

先说结论,再解释:

  • 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__函数

到这里你应该对iteratoriterable有了比较好的了解。这里涉及到两个类:

  • 一个对象是仓库,也就是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

上面的例子,我们定义了两个类。很多时候为了方便,我们会让一个类把两件事情都做了,毕竟只要这个类同时有__iternext__函数就可以了。

我们现在定一个随机数生成器,我把它命名为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 

这个类同时包含了__iternext__两个函数,自己是仓库,又是仓库管理员。就是为了代码方便点,毕竟确实没必要写到两个类里。

使用上面的Randable类:

for i in Randable():
    print(i)

执行结果:


到这里,你对iterable和iterator的理解应该很透彻了吧。

推荐阅读

睡在我上铺的室友用python,一个月挣了我一学期的生活费

以上是关于Python小知识:迭代器 vs 可迭代对象 vs 生成器的主要内容,如果未能解决你的问题,请参考以下文章

python迭代器和可迭代对象

python迭代器和可迭代对象

python迭代器和可迭代对象

复习迭代器 VS 生成器

迭代器VS生成器

python map vs itertools.map:使迭代器版本表现得像前者