zoukankan      html  css  js  c++  java
  • 048 Python里面yield的实现原理

    python 生成器yield的总结

    yeild的原理:一个带有yield的函数就是一个generator生成器(python 的 generator 只保留栈帧上下文,不保留调用栈,而且 generator 函数不允许 return;),和普通的函数不一样。只有调用next()函数的时候才会执行函数语句,在for循环中会自动调用next()方法。函数执行过程中遇到一个yield会中断一次,返回一个迭代值,函数保存自己的变量和状态,下次迭代时(也就是下次next()的时候)从yield下一条语句继续执行(这个性质很重要),函数恢复之前状态,直到遇到下一个yield返回迭代值,这样循环。
    (1):通常的for..in...循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。他可以是a = [1,2,3],也可以是a = [x*x for x in range(3)]。
    它的缺点也很明显,就是所有数据都在内存里面,如果有海量的数据,将会非常耗内存。
    (2)生成器是可以迭代的,但是只可以读取它一次。因为用的时候才生成,比如a = (x*x for x in range(3))。!!!!注意这里是小括号而不是方括号。
    (3)生成器(generator)能够迭代的关键是他有next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。
    (4)带有yield的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代
    (5)yield是一个类似return 的关键字,迭代一次遇到yield的时候就返回yield后面或者右面的值。而且下一次迭代的时候,从上一次迭代遇到的yield后面的代码开始执行
    (6)yield就是return返回的一个值,并且记住这个返回的位置。下一次迭代就从这个位置开始。
    (7)带有yield的函数不仅仅是只用于for循环,而且可用于某个函数的参数,只要这个函数的参数也允许迭代参数。
    (8)send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式值。
    (9)send()和next()都有返回值,他们的返回值是当前迭代遇到的yield的时候,yield后面表达式的值,其实就是当前迭代yield后面的参数。
    (10)第一次调用时候必须先next()或send(),否则会报错,send后之所以为None是因为这时候没有上一个yield,所以也可以认为next()等同于send(None)
    

    深入理解 Python yield

    https://blog.csdn.net/lftaoyuan/article/details/78915518
    python2和python3是不兼容的,通篇环境都是python3.6

    简单的yield实例

    以前只是粗略的知道yield可以用来为一个函数返回值塞数据,比如下面的例子:

    def addlist(alist):
        for i in alist:
            yield i + 1
    

    取出alist的每一项,然后把i + 1塞进去。然后通过调用取出每一项:

    alist = [1, 2, 3, 4]
    for x in addlist(alist):
        print(x)
    

    这的确是yield应用的一个例子,但是,看过很多东西,并自己反复体验后,对yield有了一个全新的理解,其中这篇算是精品了。

    包含yield的函数

    假如你看到某个函数包含了yield,这意味着这个函数已经是一个Generator,它的执行会和其他普通的函数有很多不同。比如下面的简单的函数:

    def h():
        print('study yield')
        yield 5
        print('go on!')
    
    h()
    

    可以看到,调用h()之后,print 语句并没有执行!这就是yield。具体的内容后面会越来越清晰,包括yield的工作原理。

    yield是一个表达式

    python 2.5以前,yield是一个语句,我也没有考证,因为早都不用了,现在yield是一个表达式:

    m = yield 5
    

    表达式(yield 5)的返回值将赋值给m,所以,m = 5 肯定是错的。
    那么如何获取(yield 5)的返回值呢?需要用到send(msg)

    yield工作原理

    揭晓yield的工作原理,需要配合next()函数。上面的h()被调用后并没有执行,因为它有yield表达式,通过next()可以恢复Generator执行,直到下一个yield

    def h():
        print('study yield')
        yield 5
        print('go on!')
    c = h()
    d1 = next(c)  # study yield
    d2 = next(c)
    """
    study yield
    go on!
    Traceback (most recent call last):
      File "D:/idea/workspace/pythonSpace/PythonDemo/static/yield_demo.py", line 35, in <module>
        d2 = next(c)
    StopIteration
    """
    
    • 1
    • 2
    • 3
    • 4
    next()`被调用后,`h()`开始执行,直到遇到`yield 5
    

    因此输出结果是:study yield
    当我们再次调用next()时,会继续执行,直到找到下一个yield。由于后面没有yield了,因此会拋出异常:

    study yield
    go on!
    Traceback (most recent call last):
      File "D:/idea/workspace/pythonSpace/PythonDemo/static/yield_demo.py", line 35, in <module>
        d2 = next(c)
    StopIteration
    

    send(msg) 与 next()

    了解了next()如何让包含yield的函数执行后,我们再来看另外一个非常重要的函数send(msg)
    其实next()send()在一定意义上作用是相似的
    区别
    send()可以传递yield的值
    next()只能传递None
    所以next()send(None)作用是一样的。

    def s():
        print('study yield')
        m = yield 5
        print(m)
        d = yield 16
        print('go on!')
    c = s()
    s_d = next(c)  # 相当于send(None)
    c.send('Fighting!')  # (yield 5)表达式被赋予了'Fighting!'
    

    输出的结果为:

    study yield
    Fighting!
    

    注意 生成器刚启动时(第一次调用),请使用next()语句或是send(None),不能直接发送一个非None的值,否则会报TypeError,因为没有yield语句来接收这个值。

    send(msg) 与 next()的返回值

    send(msg)next() 的返回值比较特殊,是下一个yield表达式的参数(yield 5,则返回 5)。
    到这里,第一个例子中,通过for i in alist 遍历 Generator,其实是每次都调用了next(),而每次next()的返回值正是yield的参数:

    def s():
        print('study yield')
        m = yield 5
        print(m)
        d = yield 16
        print('go on!')
    c = s()
    s_d1 = next(c)  # 相当于send(None)
    s_d2 = c.send('Fighting!')  # (yield 5)表达式被赋予了'Fighting!'
    print('My Birth Day:', s_d1, '.', s_d2)
    

    输出结果:

    study yield
    Fighting!
    My Birth Day: 5 . 16
    

    中断Generator

    上面的例子中,当没有可执行程序的时候,会抛出一个StopIteration, 开发过程中,中断Generator是一个非常灵活的技巧

    throw

    通过抛出一个GeneratorExit异常来终止Generator。

    close

    close的作用和throw是一样的,看它的源码,可以发现,它和raise一球样

    def throw(self, type, value=None, traceback=None):
        '''Used to raise an exception inside the generator.'''
        # 用于在生成器中抛出一个异常。
        pass
    def close(self):
        '''Raises new GeneratorExit exception inside the generator to terminate the iteration.'''
        # 在生成器中生成新的GeneratorExit异常来终止迭代。
        pass
    

    其实最后一个中断生成器可以忽略的,在开发过程中,不可避免的要用到这些,但是Python3内部已经做得很好了,一般不太需要手动去做这件事情。

    demo地址

    https://github.com/seeways/PythonDemo/blob/master/static/yield_demo.py

  • 相关阅读:
    NTP on FreeBSD 12.1
    Set proxy server on FreeBSD 12.1
    win32 disk imager使用后u盘容量恢复
    How to install Google Chrome Browser on Kali Linux
    Set NTP Service and timezone on Kali Linux
    Set static IP address and DNS on FreeBSD
    github博客标题显示不了可能是标题包含 特殊符号比如 : (冒号)
    server certificate verification failed. CAfile: none CRLfile: none
    删除文件和目录(彻底的)
    如何在Curl中使用Socks5代理
  • 原文地址:https://www.cnblogs.com/abdm-989/p/14398404.html
Copyright © 2011-2022 走看看