zoukankan      html  css  js  c++  java
  • 啊哈,yield

    文章出处:http://www.cnblogs.com/winstic/,请保留此连接

    在python编程中,我们经常会看到函数中带有yield关键字,但请注意,此时的函数不再是我们熟知的一般函数,而是所谓的generator(生成器)


      

    生成器

      对于生成器,可以对比于列表来看,我们在循环代码中经常会使用range()产生一个list对象,继而在for循环下依次遍历,

    for i in range(1000):
        print i

      或者是使用列表生成式生成一个list对象:

    [x * x for x in range(1000)]

      这么做确实很方便,但这有个很大的缺点,我们所生产的list对象在程序运行过程中是存放在内存中的,占用内存大小与list规模有关,若要在编程时控制内存的占用,最好不要使用list。

      相比于list对象对内存的占用,generator有很大的优势,generator保存的是算法,不会生成所有的元素,而只是在调用next()时产生一个元素,很好的优化了内存占用的问题,可以通过next方法访问数据,当没有数据时会自动抛出StopIteration异常

    >>> gener = (x * x for x in [1, 2, 3])
    >>> g = (x * x for x in [1, 2, 3])
    >>> g
    <generator object <genexpr> at 0x02534968>
    >>> g.next()
    1
    >>> g.next()
    4
    >>> g.next()
    9
    >>> g.next()
    
    Traceback (most recent call last):
      File "<pyshell#16>", line 1, in <module>
        g.next()
    StopIteration
    >>> 

      这么做的话难免有些繁琐,还好在for循环中会帮我们实现next方法的调用也可以这么实现

    >>> for i in g:
        print i

    yield 初体验

      以上所实现的generator只是规律十分简单的,这很好实现,只需要类似于列表生成式的简单语法即可,那么对于其他的数列计算如何实现呢,例如斐波那契数列,它的定义虽然简单:除第一、二个数据外,所有的数据均是其前两个数据之和;如果我们通过一般函数实现,无疑当数列规模很大时,占用大量内存

    >>> def fib(N):
        n, a, b = 0, 0, 1
        while n < N:
            print b
            a, b = b, a + b
            n = n + 1

      那么如何将上述方法转换为generator加以实现呢,很简单,只需要将print b 替换为yield b即可,我们可以试一下:

    >>> def fib(N):
        n, a, b = 0, 0, 1
        while n < N:
            yield b
            a, b = b, a + b
            n = n + 1
    
            
    >>> fib(5)
    <generator object fib at 0x02530F80>
    >>> for i in fib(5):
        print i
    
        
    1
    1
    2
    3
    5

      加了yield关键字后的函数是如何执行的呢,不应该说是函数,这时应该称为generator;我们调用fib(5)并不会执行函数,而是返回一个generator对象,真正的执行是在调用next方法(for循环中自动调用next()),每次循环都会执行fib内的代码,遇到yield则返回一个迭代值(类似于中断);在下次循环时执行yield的下一语句,直至遇到下一个yield。


    yield 协程

      协程(coroutine)也叫微线程,相比于多线程更为高效,因为协程是多个程序在一个线程中执行,没有线程间切换的开销;同时在协程中不需要加锁机制,因为在一个线程中不存在变量冲突问题。

      例如经典问题(生产者-消费者问题)就可以使用协程机制实现,相比于多线程更为高效

    def consumer():
        r = ''
        while True:
            n = yield r
            if not n:
                return
            print 'consumer %s' % n
            r = 'OK'
    
    def produce(c):
        c.next()
        n = 0
        while n < 5:
            n = n + 1
            print 'produce %s' % n
            r = c.send(n)
            print 'consumer return %s' % r
        c.close()
    
    if __name__ == '__main__':
        c = consumer()
        produce(c)
    produce 1
    consumer 1
    consumer return OK
    produce 2
    consumer 2
    consumer return OK
    produce 3
    consumer 3
    consumer return OK
    produce 4
    consumer 4
    consumer return OK
    produce 5
    consumer 5
    consumer return OK
    执行结果

      在上述代码中,consumer是一个生成器,执行过程中首先通过consumer产生generator对象c,

      我们在执行到produce(c)的next方法时,才切换到生成器函数consumer中执行,

      在consumer中遇到yield中断,又切回到produce中

      在produce中的c.send(n):主要干两件事:1.将n添加到生成器中,2.返回下一个yield值(return next());所以当我们运行到send方法时,内含next机制进而切换到consumer函数中执行(传入参数n),得到返回值'OK'(在下一个yield中返回)

      。。。。。

      最后在produce中关闭迭代器c.close()

    参考:         http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868328689835ecd883d910145dfa8227b539725e5ed000


    总结:

    1. 在generator中不同于一般函数,调用方法名不会执行,只会返回一个generator对象,只有在调用next方法时才会执行
    2. 一个函数中加入yield则变为generator,函数执行到yield时中断,下次迭代时定位到yield的下一条语句;yield还常用于文件的读取,用read()会造成不可预测的内存占用问题,而使用yield可以实现内存只存储每次迭代过程中固定的size
  • 相关阅读:
    【转】Windows守护进程的一种简单实现
    vim 文本会在末尾自动添加换行 md5文件和数据只不对应
    指向指针的指针的理解和应用
    TinyXML C++解析XML
    加密解密 AES RSA MD5 SHA
    微信支付 php兼容问题
    sublime text 2 php 语法错误检查
    微信支付宝支付
    MySql安装和基本管理
    验证码处理
  • 原文地址:https://www.cnblogs.com/winstic/p/4162640.html
Copyright © 2011-2022 走看看