什么是迭代器与生成器,它们有什么区别?

图片[1]-什么是迭代器与生成器,它们有什么区别?-不念博客
迭代器与生成器

迭代器(iterator)

定义

迭代是可以通过遍历的方式依次把某个对象中的元素取出的方法,在python中,迭代是通过使用for…in…语句完成的

可迭代对象

能够被for循环的对象就是可迭代对象

  • 集合数据类型:str,list,tuple,dict,set…
  • 生成器(generator),包括生成器和带yield的生成器函数

其实可迭代对象有更具体的定义方式 – 但凡内置方法中有__iter__和__next__方法的对象就是迭代器对象。

执行迭代器对象的__iter__方法得到的仍然是迭代器对象,执行迭代器对象的__next__方法就会得到迭代器对象中的下一个值。

注意: 是可迭代对象的,不一定是迭代器,是迭代器的,一定是可迭代对象,可以通过isinstance函数对其进行判断

from collections import Iterator

print(isinstance("123", Iterator))  # str类型,输出:False

如果我们想把他们转换为迭代器,可以使用iter函数

from collections import Iterator

print(isinstance(iter("123"), Iterator))  # str类型,输出:True

迭代对象的遍历

  1. 使用常规for语句进行遍历:
list = [1, 2, 3, 4]
it = iter(list)  # 创建迭代器对象
for x in it:
    print(x, end=" ")
    
 # 输出
 1 2 3 4 
  1. 使用 next() 函数:
import sys  # 引入 sys 模块

list = [1, 2, 3, 4]
it = iter(list)  # 创建迭代器对象

while True:
    try:
        print(next(it), end=" ")
    except StopIteration:
        sys.exit()

# 输出
1 2 3 4 

迭代器中有两个基本方法:iter(),next()。使用iter函数创建一个迭代器后,就可以通过next函数获取迭代器的下一个值,如果通过next()不断调用并返回下一个值,那么等到最后没有下一个值了,就会抛出异常:StopIteration

在python中,for语句本质就是不断调度next函数实现的,for循环的对象一定是可迭代对象,因此for循环也被称为迭代器循环,for循环的原理就是基于迭代器的。

使用迭代器来实现一个斐波那契数列

# -*- coding: utf-8 -*-


class Fibonacci:
    def __init__(self, n):
        self.n = n
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.n > 0:
            self.n -= 1
            self.a, self.b = self.b, self.a + self.b
            return self.a
        else:
            raise StopIteration


b = Fibonacci(11)
for i in b:
    print(i)  

# 输出:
1 1 2 3 5 8 13 21 34 55 89

注:我们需要在类中写两个魔法方法,iter__和__next,如果没有写,则会报错

生成器 (generator)

定义

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

函数用return关键字来返回值,将函数体代码中的return关键字替换成yield关键字,再调用函数,不会执行函数体代码,得到的返回值就是一个生成器对象。
得到的生成器(generator)对象有内置方法__iter__和__next__,因此生成器本身就是自定义的迭代器。

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

生成器表达式

生成器表达式和列表推导式差不多,我们只需要包列表推导式的[]改为(),这样就是一个生成器表达式了。需要注意的是,列表推导式返回的是一个列表对象,而生成器表达式返回的是一个生成器对象,因此我们可以通过生成器表达式来创建一个生成器。优点自然是节省内存,一次只产生一个值存在内存中。

a = [i for i in range(1, 4)]
print(type(a))  # <class 'list'>
print(a)  # [1, 2, 3]
print(next(a)) # TypeError: 'list' object is not an iterator

b = (i for i in range(1, 4))
print(type(b))  # <class 'generator'>
print(b)  # <generator object <genexpr> at 0x0000021781CCEE40>
print(next(b)) # 1

生成器函数

将函数体代码中的return关键字替换成yield关键字,就有了自定义迭代器的实现方式,yield不同于return,函数一旦遇到return就结束了,但是yield可以保存函数的运行状态,用来返回多次值。

# -*- coding: utf-8 -*-
# 自定义一个可以产生很多数字的生成器,就像range一样
def my_range(start, stop, step=1):
    while start < stop:
        yield start
        start += step


res = my_range(1, 10, 2)
print(res)  # <generator object my_range at 0x0000028FF2F5A6C8>
print(res.__iter__())  # <generator object my_range at 0x0000028FF2F5A6C8>
print(res.__next__())  # 1
print(res.__next__())  # 3
print(res.__next__())  # 5

使用生成器来实现一个斐波那契数列

# -*- coding: utf-8 -*-
def Fibonacci(n):
    b = 0
    c = 1
    while True:
        if n > 0:
            n -= 1
            b, c = c, b + c
            yield b
        else:
            break


#
res = Fibonacci(11)
print(res.__next__())
print(res.__next__())
print(res.__next__())
print('--------------------')
for i in res:
    print(i)


# 输出
1
1
2
--------------------
3
5
8
13
21
34
55
89

使用yield读取文件

如果直接对文件对象调用read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:

# -*- coding: utf-8 -*-
def read_file(path, encoding="utf-8"):
    BLOCK_SIZE = 1024
    with open(path, "r", encoding=encoding) as f:
        block = f.read(BLOCK_SIZE)
        if block:
            yield block
        else:
            return None


res = read_file('card.txt')
for i in res:
    print(i)
    
# 输出
0011527546,201,2001 #
0002470753,302,0000 #
3375355472,4,0000 #

send 方法

send 方法可以把外部的值传入生成器内部,从而改变生成器的状态。

# -*- coding: utf-8 -*-
import sys
import time


def gen():
    i = 1
    while True:
        j = yield i
        i *= 2
        if j == -1:
            break


g = gen()  # 创建一个生成器
print(g.__next__())  # 1
print(g.__next__())  # 2
for i in g:
    print(i)
    if i == 32:
        try:
            # send 把 -1 传入生成器内部 走到了 j = -1 这个分支
            print('我提前结束了')
            print(g.send(-1))  # StopIteration 迭代停止
        except StopIteration:
            sys.exit()

    time.sleep(1)


# 输出
1
2
4
8
16
32
我提前结束了

当我们执行 g.send(-1) 时,相当于把 -1 传入到了生成器内部,然后赋值给了 yield 前面的 j,此时 j = -1,然后这个方法就会 break 出来,不会继续迭代下去。否则输出结果会是 1 2 4 8 16 32 64 … 一直循环下去, 直到我们杀死这个进程才能停止。

© 版权声明
THE END
喜欢就支持一下吧
点赞129赞赏 分享
评论 抢沙发
头像
欢迎光临不念博客,留下您的想法和建议,祝您有愉快的一天~
提交
头像

昵称

取消
昵称

    暂无评论内容