Python:迭代器和生成器的编程模型 - 以阴阳谜题为例

笨比试图理解Scheme实现的阴阳谜题未果,先针对比较简单的Python展开理解🥺

本文介绍了理解Python求解阴阳谜题的语法基础,然后讲解了Python求解阴阳谜题的代码,包括详细的执行过程。

迭代器

如何理解迭代器?
迭代器可以理解成一个简单的类,只不过实现了__iter__()__next__()这两个特殊的接口而已。__iter__() 方法一般就是返回这个迭代器的「初始状态」,__next__() 方法会返回一个值并更新迭代器的状态。而iter可以视为一种创建迭代器对象的方法,比如iter([1,2,3,4])是从列表创建了一个迭代器,只是因为iter实现了从列表构造。1

因为自己之前了解过stream,所以不难理解迭代器的作用是实现一种类似「无穷列表」之类的东西,以及自然联想到迭代器可以用各种姿势截断前n个值作为一个「有限列表」。
实际上迭代器已经比stream更强大,他可以和一个「有穷列表」等价。具体的实现机制是StopIteration,迭代器靠这个实现了iterable,因此可以用在for循环里。
具体来说,当迭代器出现在for循环里时,for循环一直从迭代器里通过.__next__()取值,直到遇到StopIteration为止。2迭代器用于for循环有以下注意事项:

  1. 迭代器可能是无穷的,即对标stream。这对于用过Haskell的我来说很容易接受。
  2. 我们平时常用的大部分iterable都是indexable,但是迭代器并不是。

关于迭代器有一个python官方的包itertools,实现一系列 iterator ,这些迭代器受到APL,Haskell和SML的启发。为了适用于Python,它们都被重新实现。3

生成器

如何理解生成器?

生成器是一种迭代器,这种迭代器是通过「使用了 yield 的函数」构造的。
众所周知,我们在使用一个函数时,在作用域内的变量、代码段和代码段运行到的位置构成了「环境」。当对这个迭代器取next的时候,我们是去运行这个代码段,直到运行到yield输出值,或者运行到return则raise StopIteration。

之所以要用yield构造迭代器,是因为yield通常是用来快速地把一个函数改成一个生成器。参考这个链接4,用这种方式构造生成器,代码会简单很多。

生成器有以下特殊语法:

  • send
    • 在调用__next__()方法从生成器取一个值之后,其实会给yield xxx这句话返回一个None作为求值结果。如果把.__next__()相应地换成.send(yyy),外部会先把yyy返回给生成器函数,赋给当前的yield这句;然后进行一次yield,把产生的值赋给外部代码send的那句。以此法,生成器内部便可以支持a = yield b的写法。5
    • send注意事项
      • generator的next和send是两个独立的操作。当主代码中使用send的时候,默认generator里面的代码停在一个yield语句处,send把参数值传给generator的对应位置,而不运行里面的代码。假如在下一次yield值之前又进行了一次send,后面这次send会重新赋值,相当于覆盖掉前面那次send的成果。而当generator需要yield一个值的时候,generator运行里面的代码直到下一次yield,在这过程中会正常使用环境里变量的当前值,跟是否用send赋值过没有关系。
      • 因为当主代码中使用send的时候,默认generator里面的代码停在一个yield语句处,所以在实例化生成器后如果直接调用send()方法启动函数,应该使用send(None)方法,因为此时没有yield表达式可以接收其返回的值。或者在实例化生成器后首先使用next()函数。
  • yield from
    • 生成器内部写yield from后面跟一个生成器,这样这个生成器需要yield一个值的时候,会实例化后面的子生成器,并等待接受这个子生成器yield出来的一个值,将其yield出去。6
  • 另外还有throw和close方法,比较好理解,就不多说了。还是参考链接5

阴阳谜题

Python对阴阳谜题的实现是不断新建generator的过程,每个generator负责yield一个值并新建一个新的generator。7

def puzzle():
    def yin(arg_yin):
        yield '@'
        def yang(arg_yang):
            yield '*'
            yield from arg_yin(arg_yang)
        yield from yang(yang)
    yield from yin(yin)

a = puzzle()
for i in range(30):
    print(a.__next__(), end='')
  • 在第1次需要yield值的时候
    • line 2-7 被执行,定义了yin
    • line 8,创建生成器yin1(arg_yin=yin),并等待从yin1 yield一个值
    • 在创建的生成器yin1中line 3,yield ‘@’
  • 在第2次需要yield值的时候
    • 在yin1中line 4-6,定义了yang_1,这个定义中arg_yin=yin
    • 在yin1中line 7,通过yang_1创建yang1=yang_1(arg_yang=yang_1),并等待从yang1 yield一个值。yang1中arg_yin=yin
    • 在yang1中line 5,yield ‘*’
  • 在第3次需要yield值的时候
    • 在yang1中line 6,创建yin(arg_yin=yang_1),即yin2. 等待从yin2 yield一个值
    • 在yin2中line 3,yield ‘@’
  • 在第4次需要yield值的时候
    • 在yin2中line 4-6,定义yang_2,这个定义中arg_yin=yang_1
    • 在yin2中line 7,创建yang2=yang_2(arg_yang=yang_2)。yang2中arg_yin=yang_1
    • 在yang2中line 5,yield ‘*’
  • 在第5次需要yield值的时候
    • 在yang2中line 6,创建yang2-1=yang_1(arg_yang=yang_2)
    • 在yang2-1中line 5,yield ‘*’
  • 在第6次需要yield值的时候
    • 在yang2-1中line 6,yin3=yin(arg_yang=yang_2)
    • 在yin3中line 3,yield ‘@’
  • 在第7次需要yield值的时候
    • 在yin3中line 4-7,定义yang_3,这个定义中arg_yin=yang_2
    • 在yin3中line 7,创建yang3=yang_3(arg_yang=yang_3)。yang3中arg_yin=yang_2
    • 在yang3中line 5,yield ‘*’
  • 在第8次需要yield值的时候
    • 在yang3中line 6,创建yang3-1=yang_2(arg_yang=yang_3)
    • 在yang3-1中line 5,yield ‘*’
  • 在第9次需要yield值的时候
    • 在yang3-1中line 6,yang3-2=yang_1(arg_yang=yang_3)
    • 在yang3-2中line 5,yield ‘*’
  • 在第10次需要yield值的时候
    • 在yang3-2中line 6,yin4=yin(arg_yang=yang_3)
    • 在yin4中line 3,yield ‘@’

写到这里就差不多了,可以看到每一个yin在执行的时候构造了一个更高级嵌套的yang,然后每一个yang在执行的时候unfold出一个低级的yang. 如果还是不够清楚的话可以把每一个yield from后面的generator的id打出来看。

距离解析Scheme的阴阳谜题已经不远啦,只要能对照这篇文章搞清楚Scheme宏和call/cc的编程模型就可以理解了。加油!

参考文献


  1. https://www.runoob.com/python3/python3-iterator-generator.html ↩︎

  2. https://www.liaoxuefeng.com/wiki/1016959663602400/1017318207388128 ↩︎

  3. https://docs.python.org/zh-cn/3/library/itertools.html ↩︎

  4. https://www.runoob.com/w3cnote/python-yield-used-analysis.html ↩︎

  5. https://wenkefendou.gitbooks.io/python3-learning/content/generator_and_yield.html ↩︎ ↩︎

  6. https://wenkefendou.gitbooks.io/python3-learning/content/yield_from.html ↩︎

  7. https://zhuanlan.zhihu.com/p/43207643 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值