Python进阶到高级:迭代器和生成器

2023年 8月 18日 36.1k 0

1、迭代协议

迭代就是可以使用循环将数据挨个挨个取出来,这个好理解是吧,比如,咱们常见的对一个列表进行迭代:

for i in [1, 2, 3]:
    print(i)

结果不用讲肯定是挨着取出列表里面的数字了。

那列表里面究竟是实现了什么协议,或者说一个对象实现什么魔法函数就可以迭代呢,这就是迭代协议:__iter__

一个类只要实现了魔法函数 __iter__ 就是可迭代的(Iterable),但是它还不是迭代器(Iterator),品一下区别。

class IterTest:

    def __iter__(self):
        ...

来验证一下:

from collections.abc import Iterable
from collections.abc import Iterator

print("是否可迭代:", isinstance(IterTest(), Iterable))
print("是否为迭代器:", isinstance(IterTest(), Iterator))
是否可迭代:True
是否为迭代器:False

你看实现了迭代协议,就是可迭代的,想起鸭子类型了吗。

2、迭代器和可迭代对象

我们现在知道一个对象只要实现了 __iter__ 就是一个可迭代的对象,现在咱们来试试对一个可迭代对象使用 for 循环进行迭代,放个简单的列表进去看看:

class IterTest:

    def __iter__(self):
        return [1, 2, 3]

for i in IterTest():
    print(i)

__iter__ 函数里面返回一个列表,列表是一个可迭代的对象,但不是迭代器。

Traceback (most recent call last):
  File "/tmp/pycharm_project_609/123.py", line 11, in 
    for i in IterTest():
TypeError: iter() returned non-iterator of type 'list'

运行报错了,说 iter 返回了一个不是迭代器的对象。说明在 __iter__ 里面需要返回一个迭代器,对吧,其他的先不管,咱们放一个迭代器进去,保证程序跑起来不报错。

放一个生成器表达式进去试试:

class IterTest:

    def __iter__(self):
        return (i for i in range(3))

for i in IterTest():
    print(i)
0
1
2

唉,这下对了,没报错,而且也能迭代出来了。

但是,此时仍然还不是一个迭代器,要实现迭代器,还必须要实现另外一个魔法函数:__next__

class IterTest:

    def __iter__(self):
        return (i for i in range(3))

    def __next__(self):
        ...

验证一下

print("是否为迭代器:", isinstance(IterTest(), Iterator))
是否为迭代器: True

你看,实现 __next__ 之后,就是一个迭代器了。那 __next__ 应该怎么写,前面我们已经看到, __iter__ 里面是不负责逻辑处理的,它只管返回,逻辑处理需要在 __next__ 里面去做。

使用经典的斐波那契数列来举例:

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

    # 返回迭代器对象本身
    def __iter__(self):
        return self

    # 返回容器下一个元素
    def __next__(self):
        if self.n > 0:
            self.a, self.b = self.b, self.a + self.b
            self.n -= 1
            return self.a
        else:
            raise StopIteration

这里面 n 是用来限制迭代次数的,不然这个循环将一直进行下去,直到宇宙的尽头,抛 StopIteration 异常会被 for 循环自动处理掉。

for i in Fib(10):
    print(i)
1
2
3
5
8
13
21
34
55

这样我们就实现了一个简单的迭代器。

简单一句话总结一下:迭代器就是使对象可以进行 for 循环,它需要实现 __iter____next__ 两个魔法函数。

有同学要说了,就这?不就用 for 循环嘛,搞这么复杂嘎哈,我为什么要用迭代器啊?

为什么要使用迭代器

节省资源消耗,迭代器并不会计算每一项的值,它只在你访问这些项的时候才计算,也就是说它保存的是一种计算方法,而不是计算的结果。能理解吗,相当于迭代器是鱼竿,而不是一池子的鱼,需要鱼的时候钓就行了,而不用把所有鱼都搬回家。

平时可能感受不到哈,当你需要计算一个非常大的数据时,你就能感受到了,这就是“惰性求值”的魅力。

你可以试试前面的斐波那契数列的列子,对比一个普通的列表,然后给一个很大的数字,区别就很明显了。

3、生成器

生成器也是一种迭代器,特殊的迭代器,它也可以用 for 循环来取值,但是大部分的情况下是使用 next() 函数进行取值。

前面我们讲生成器表达式已经见识过,这是一种便携的写生成器的方法:

my_gen = (i for i in range(10))
print(next(my_gen))
print(next(my_gen))
print(next(my_gen))
print(next(my_gen))
0
1
2
3

一般这么玩的哈。

前面讲的好多对象都是在类里面定义的,而生成器对象就不是在类里面了,而是在函数里面定义,在一个函数里面只要出现了 yield 它就不是普通函数,而是一个生成器。

def my_gen():
    print("setp 1")
    yield 1
    print("setp 2")
    yield 2

g = my_gen()
next(g)
next(g)
step 1
step 2

yield 的用途是让函数暂停,并保存对象状态在内存中,下次再使用 next 调用同一个对象时,又开始从之前暂停的位置开始执行,直到运行到下一个 yield 又暂停,如果后面没有 yield了,则会抛 StopIteration 异常。

yieldreturn 都能返回数据,但是有区别,return 语句之后的代码是不执行的,而 yield 后面还可以执行。

有同学要问了,生成器函数里面能用 return 吗?好问题,不愧是你。

生成器里面是可以用 return 的,但是,return 后面的数据不会被返回。

举例:

def my_gen():
    yield 1
    yield 2
    return 3

for i in my_gen():
    print(i)
1
2

你看,3 并没有被返回,所以说生成器里面的 return 只是一个结束的标志,它不会把后面的值返回给调用者,这跟函数里面的 return 是不一样的。

4、总结

看完前面迭代器和生成器的内容,可能有些同学有点晕了,没关系,多看几遍,经常看,经常晕。

我们简单总结一下:

  • 迭代器需要实现两个魔法函数:__iter____next__
  • 迭代器允许惰性求值,只有在请求下一个元素时迭代器对象才会去生成它,它保存的是一种生成数据的方法;
  • 生成器是迭代器的一种更 Pythonic 的写法,可以在函数里面用 yield 创建一个迭代器;
  • 生成器表达式是生成器的一种更加 Pythonic 的写法。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论