迭代器与生成器

迭代器(iterator)

定义

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

Python中的 for 循环本质上是一种遍历迭代器(iterator)中元素的方式。在每次循环迭代时,Python会自动调用迭代器的 __next__() 方法,将其所返回的下一个元素赋值给循环变量,并执行循环体中的语句。当迭代器中的元素全部被遍历完时,for 循环自动停止迭代。

可迭代对象

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

  • 集合数据类型:str,list,tuple,dict,set…

  • 生成器(generator),包括生成器和带yield的生成器函数

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

其中,__iter____next__ 是迭代器类中必须实现的两个方法。__iter__ 方法返回当前对象本身作为迭代器,并在迭代开始前进行初始化;__next__ 方法返回下一个迭代器值,如果到达迭代器末尾,则触发 StopIteration 异常。

具体来说,在 Python 中,实现迭代器的过程如下:

  1. 定义一个迭代器类,并在 __init__ 方法中初始化迭代器状态。

  2. 实现 __iter__ 方法,返回当前对象本身作为迭代器。

  3. 实现 __next__ 方法,返回下一个迭代器值。

  4. 在需要时使用迭代器遍历集合中的元素。

注意: 是可迭代对象的,不一定是迭代器,是迭代器的,一定是可迭代对象,可以通过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

具体来说,iter() 函数会调用对象的 __iter__() 方法,而 next() 函数则会调用迭代器对象的 __next__() 方法

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

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

斐波那契数列是指这样一个数列:1,1,2,3,5,8,13,21,34,55,89...... 这个数列从第三项开始,每一项都等于前两项之和

# -*- 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__,如果没有写,则会报错

需要注意的是,如果一个对象实现了 __iter__ 方法,那么该对象就是可迭代的,也就是说,我们可以使用 iter() 函数获取其迭代器。如果对象没有实现 __iter__ 方法,但是实现了 __getitem__ 方法,那么该对象也是可迭代的,它的迭代器会按照索引依次获取元素。

class Fibonacci:
    def __getitem__(self, index):
        a, b = 1, 1
        for _ in range(index):
            a, b = b, a + b
        return a


fib = Fibonacci()
for i in range(10):
    print(fib[i])

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

Process finished with exit code 0

__getitem__ 是 Python 的一个魔法方法(Magic Method),它用于实现索引运算符 [] 的行为。通过在一个类中定义 __getitem__ 方法,可以使该类的对象支持索引操作,可以像列表或字典一样使用中括号访问其元素

__getitem__ 方法的具体语法如下:

Codedef __getitem__(self, key):
    # 索引操作的具体实现

其中,key 表示要获取元素的下标或键,我们需要根据该参数在对象中找到对应的值并将其返回。

在 Python 中,很多内置的数据类型(如列表、字典、元组等)都已经实现了 __getitem__ 方法,因此我们可以直接使用中括号来访问这些对象的元素。此外,也可以在自定义的类中实现该方法,以使得该类的对象支持索引操作。

生成器 (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 方法可以把外部的值传入生成器内部,从而改变生成器的状态。

send(value) 方法:在生成器中向 yield 语句发送值,并恢复其执行。如果生成器在 yield 语句处等待,那么该值将成为 yield 表达式的结果。可以通过 next() 方法或者 __next__() 方法来启动生成器并将第一个值发送给它。示例如下:

# -*- coding: utf-8 -*-
def foo():
    while True:
        x = yield
        print(x)

f = foo()
next(f)
f.send('Hello')

# 输出
Hello

在生成器函数中,第一次调用 next() 或者 send(None) 是必须的,因为它们会启动生成器并运行到第一个 yield 语句处等待进一步的指令。如果直接调用 send() 方法,而没有先使用 next() 启动生成器,则会得到一个 TypeError 异常。

close() 方法

close() 方法:用于关闭生成器,释放相关资源并结束其执行。

一个示例代码如下所示,其中我们定义了一个生成器函数 foo ,它不断地产生斐波那契数列元素。在主程序中,我们使用 for 循环遍历该生成器对象,并通过 break 语句提前结束循环。接着,我们调用生成器对象的 close() 方法来关闭生成器。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2023/4/22 9:26
# @Author  : shisuiyi
# @File    : demo_推导式.py
# @Software: win10 Tensorflow1.13.1 python3.9

def foo():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b


f = foo()
for x in f:
    print(x)
    if x > 100:
        f.close()
        break

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

Process finished with exit code 0

throw 方法

throw(type[, value[, traceback]]) 方法:用于在生成器中抛出一个指定类型、值、回溯信息的异常,并恢复其执行。如果在生成器中没有处理这种类型的异常,那么异常会向上冒泡,直到被捕获或传递给调用函数。示例如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2023/4/22 9:26
# @Author  : shisuiyi
# @File    : demo_推导式.py
# @Software: win10 Tensorflow1.13.1 python3.9

def foo():
    while True:
        try:
            x = yield
            y = int(x)
            print(y ** 2)
        except ValueError as e:
            print("Invalid input:", e)


f = foo()
next(f)
f.send("5")
f.send("10")
f.send("A")
f.throw(ValueError, "Invalid input: A")
f.send("3")

# 输出
25
100
Invalid input: invalid literal for int() with base 10: 'A'
Invalid input: Invalid input: A
9

Process finished with exit code 0