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

2023-08-1808:18:09编程语言入门到精通Comments767 views字数 2894阅读模式

1、迭代协议

迭代就是可以使用循环将数据挨个挨个取出来,这个好理解是吧,比如,咱们常见的对一个列表进行迭代:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

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

结果不用讲肯定是挨着取出列表里面的数字了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

那列表里面究竟是实现了什么协议,或者说一个对象实现什么魔法函数就可以迭代呢,这就是迭代协议:__iter__文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

一个类只要实现了魔法函数 __iter__ 就是可迭代的(Iterable),但是它还不是迭代器(Iterator),品一下区别。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

class IterTest:

    def __iter__(self):
        ...

来验证一下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

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

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

你看实现了迭代协议,就是可迭代的,想起鸭子类型了吗。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

2、迭代器和可迭代对象

我们现在知道一个对象只要实现了 __iter__ 就是一个可迭代的对象,现在咱们来试试对一个可迭代对象使用 for 循环进行迭代,放个简单的列表进去看看:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

class IterTest:

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

for i in IterTest():
    print(i)

__iter__ 函数里面返回一个列表,列表是一个可迭代的对象,但不是迭代器。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

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

运行报错了,说 iter 返回了一个不是迭代器的对象。说明在 __iter__ 里面需要返回一个迭代器,对吧,其他的先不管,咱们放一个迭代器进去,保证程序跑起来不报错。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

放一个生成器表达式进去试试:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

class IterTest:

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

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

唉,这下对了,没报错,而且也能迭代出来了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

但是,此时仍然还不是一个迭代器,要实现迭代器,还必须要实现另外一个魔法函数:__next__文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

class IterTest:

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

    def __next__(self):
        ...

验证一下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

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

你看,实现 __next__ 之后,就是一个迭代器了。那 __next__ 应该怎么写,前面我们已经看到, __iter__ 里面是不负责逻辑处理的,它只管返回,逻辑处理需要在 __next__ 里面去做。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

使用经典的斐波那契数列来举例:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

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 循环自动处理掉。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

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

这样我们就实现了一个简单的迭代器。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

简单一句话总结一下:迭代器就是使对象可以进行 for 循环,它需要实现 __iter____next__ 两个魔法函数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

有同学要说了,就这?不就用 for 循环嘛,搞这么复杂嘎哈,我为什么要用迭代器啊?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

为什么要使用迭代器文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

节省资源消耗,迭代器并不会计算每一项的值,它只在你访问这些项的时候才计算,也就是说它保存的是一种计算方法,而不是计算的结果。能理解吗,相当于迭代器是鱼竿,而不是一池子的鱼,需要鱼的时候钓就行了,而不用把所有鱼都搬回家。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

平时可能感受不到哈,当你需要计算一个非常大的数据时,你就能感受到了,这就是“惰性求值”的魅力。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

你可以试试前面的斐波那契数列的列子,对比一个普通的列表,然后给一个很大的数字,区别就很明显了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

3、生成器

生成器也是一种迭代器,特殊的迭代器,它也可以用 for 循环来取值,但是大部分的情况下是使用 next() 函数进行取值。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

前面我们讲生成器表达式已经见识过,这是一种便携的写生成器的方法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

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

一般这么玩的哈。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

前面讲的好多对象都是在类里面定义的,而生成器对象就不是在类里面了,而是在函数里面定义,在一个函数里面只要出现了 yield 它就不是普通函数,而是一个生成器。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

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 异常。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

yieldreturn 都能返回数据,但是有区别,return 语句之后的代码是不执行的,而 yield 后面还可以执行。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

有同学要问了,生成器函数里面能用 return 吗?好问题,不愧是你。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

生成器里面是可以用 return 的,但是,return 后面的数据不会被返回。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

举例:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

def my_gen():
    yield 1
    yield 2
    return 3

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

你看,3 并没有被返回,所以说生成器里面的 return 只是一个结束的标志,它不会把后面的值返回给调用者,这跟函数里面的 return 是不一样的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

4、总结

看完前面迭代器和生成器的内容,可能有些同学有点晕了,没关系,多看几遍,经常看,经常晕。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

我们简单总结一下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html

  • 迭代器需要实现两个魔法函数:__iter____next__
  • 迭代器允许惰性求值,只有在请求下一个元素时迭代器对象才会去生成它,它保存的是一种生成数据的方法;
  • 生成器是迭代器的一种更 Pythonic 的写法,可以在函数里面用 yield 创建一个迭代器;
  • 生成器表达式是生成器的一种更加 Pythonic 的写法。
作者:mikigo
来源:稀土掘金
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53931.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/53931.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定