python 基础系列--可迭代对象、迭代器与生成器

迭代器是 Python 最强大的功能之一,可以想像如果有个几十 GB 的大文件,你需要编写程序处理其中的文本信息,如果一次性全部读入内存,估计机器会直接罢工了,但是借住可迭代对象,可以一次从硬盘读取一小块内容到内存,处理完后写回硬盘,不断迭代,从而节省内存,加快处理速度。

首先来解释这3个概念。
(1)可迭代对象:如果一个对象定拥有 __iter__ 方法,那么这个对象就是一个可迭代对象。这里顺便说下
for 循环的处理过程:在 Python 中我们经常使用 for 循环来对某个对象进行遍历,此时被遍历的这个对象就是可迭代对象,常见的有列表,元组,字典。for 循环开始时自动调用可迭代对象的 __iter__ 方法获取一个迭代器,for 循环时自动调用迭代器的 next 方法获取下一个元素,当调用可迭代器对象的 next 方法引发 StopIteration 异常时,结束 for 循环。

(2)迭代器:如果一个对象定拥有 __iter__ 方法和 __next__ 方法,那么这个对象就是一个迭代器。

(3)生成器:生成器是一类特殊的迭代器,就是在需要的时候才产生结果,不是立即产生结果。这样可以同时节省 CPU 和内存。有两类方法实现生成器:

  • 生成器函数。使用 def 定义函数,使用 yield 而不是 return 语句返回结果。yield 语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。

  • 生成器表达式。类似于列表推导,只不过是把一对大括号 [] 变换为一对小括号() 。但是,生成器表达式是按需产生一个生成器结果对象,要想拿到每一个元素,就需要循环遍历。

三者之间的关系如下图所示。

三者之间的关系

可迭代对象包含迭代器、序列、字典;生成器是一种特殊的迭代器。下面分别举例说明。

创建一个迭代器

class MyListIterator(object):  # 定义迭代器类,其是MyList可迭代对象的迭代器类

    def __init__(self, data):
        self.data = data  # 上边界
        self.now = 0  # 当前迭代值,初始为0

    def __iter__(self):
        return self  # 返回该对象的迭代器类的实例;因为自己就是迭代器,所以返回self

    def __next__(self):  # 迭代器类必须实现的方法
        while self.now < self.data:
            self.now += 1
            return self.now - 1  # 返回当前迭代值
        raise StopIteration  # 超出上边界,抛出异常


类 MyListIterator 实现了 __iter__ 方法和 __next__ 方法,因此它是一个迭代器对象,由于 __iter__ 方法本返的是迭代器(本身),因此它也是可迭代对象。迭代器必然是一个可迭代对象。

下面使用3种方法遍历迭代器 MyListIterator。

    my_list = MyListIterator(5)  # 得到一个可迭代对象
    print("使用for循环来遍历迭代器")
    for i in my_list:
        print(i)
    my_list = MyListIterator(5)  # 重新得到一个可迭代对象
    print("使用next来遍历迭代器")
    print(next(my_list))
    print(next(my_list))
    print(next(my_list))
    print(next(my_list))
    print(next(my_list))
    my_list = MyListIterator(5)  # 重新得到一个可迭代对象
    print("同时使用next和for来遍历迭代器")
    print("先使用两次next")
    print(next(my_list))
    print(next(my_list))
    print("再使用for,会从第三个元素2开始输出")
    for i in my_list:
        print(i)

输出结果如下:

使用for循环来遍历迭代器
0
1
2
3
4
使用next来遍历迭代器
0
1
2
3
4
同时使用nextfor来遍历迭代器
先使用两次next
0
1
再使用for,会从第三个元素2开始输出
2
3
4

从结果可以看出,for 循环实际上就是调用了迭代器的 __next__方法,当捕捉到 MyListIterator 异常时自动结束 for 循环

创建一个可迭代对象

class MyList(object):  # 定义可迭代对象类

    def __init__(self, num):
        self.data = num  # 上边界

    def __iter__(self):
        return MyListIterator(self.data)  # 返回该可迭代对象的迭代器类的实例

上例中对象 MyList 实现了 __iter__ 方法返回了迭代器类的实例,因此它是一个可迭代对象。遍历操作可使用 for 循环,无法使用 next()。for 循环实质上还是调用 MyListIterator 的 __next__ 方法。

my_list = MyList(5# 得到一个可迭代对象
print("使用for循环来遍历可迭代对象my_list")
for i in my_list:
    print(i)
    my_list = MyList(5# 得到一个可迭代对象
print("使用next来遍历可迭代对象my_list")
print(next(my_list))
print(next(my_list))
print(next(my_list))
print(next(my_list))
print(next(my_list))

输出结果如下:

使用for循环来遍历可迭代对象my_list
0
1
2
3
4
使用next来遍历可迭代对象my_list
    print(next(my_list))
TypeError: 'MyList' object is not an iterator

从运行结果知道可迭代对象如果没有 __next__方法,则无法通过next()进行遍历。

创建一个生成器

像定义一般函数一样,只不过使用 yield 返回中间结果。生成器是一种特殊的迭代器,生成器自动实现了迭代器协议,即 __iter__ 和 __next__ 方法,不需要再手动实现两方法。创建生成器实例如下:

def myList(num):  # 定义生成器
    now = 0  # 当前迭代值,初始为0
    while now < num:
        val = (yield now)  # 返回当前迭代值,
        now = now + 1 if val is None else val  # val为None,迭代值自增1,否则重新设定当前迭代值为val

遍历生成器:

my_list = myList(5)  # 得到一个生成器对象
print("for 循环遍历生成器myList")
for i in my_list:
    print(i)

my_list = myList(5)  # 得到一个生成器对象
print("next遍历生成器myList")
print(next(my_list))  # 返回当前迭代值值
print(next(my_list))  # 返回当前迭代值值
print(next(my_list))  # 返回当前迭代值值
print(next(my_list))  # 返回当前迭代值值
print(next(my_list))  # 返回当前迭代值值

运行结果如下:

for 循环遍历生成器myList
0
1
2
3
4
next遍历生成器myList
0
1
2
3
4

具有 yield 关键字的函数都是生成器,yield 可以理解为 return,返回后面的值给调用者。不同的是 return 返回后,函数会释放,而生成器则不会。在直接调用 next 方法或用 for 语句进行下一次迭代时,生成器会从 yield 下一句开始执行,直至遇到下一个 yield。

(完)

如果觉得这篇文章对您有帮助,请关注公众号 somenzz 及时获取最新消息或推荐给需要的朋友。
 somenzz 的公众号

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值