关于Python中的yield

在介绍yield前有必要先说明下Python中的迭代器(iterator)和生成器(constructor)。

一、迭代器(iterator)

在Python中,for循环可以用于Python中的任何类型,包括列表、元祖等等,实际上,for循环可用于任何“可迭代对象”,这其实就是迭代器

迭代器是一个实现了迭代器协议的对象,Python中的迭代器协议就是有next方法的对象会前进到下一结果,而在一系列结果的末尾是,则会引发StopIteration。任何这类的对象在Python中都可以用for循环或其他遍历工具迭代,迭代工具内部会在每次迭代时调用next方法,并且捕捉StopIteration异常来确定何时离开。

使用迭代器一个显而易见的好处就是:每次只从对象中读取一条数据,不会造成内存的过大开销。

比如要逐行读取一个文件的内容,利用readlines()方法,我们可以这么写:

1
2
for line in open("test.txt").readlines():
    print line

这样虽然可以工作,但不是最好的方法。因为他实际上是把文件一次加载到内存中,然后逐行打印。当文件很大时,这个方法的内存开销就很大了。

利用file的迭代器,我们可以这样写:

1
2
for line in open("test.txt"):   #use file iterators
    print line

这是最简单也是运行速度最快的写法,他并没显式的读取文件,而是利用迭代器每次读取下一行。

二、生成器(constructor)

生成器函数在Python中与迭代器协议的概念联系在一起。简而言之,包含yield语句的函数会被特地编译成生成器。当函数被调用时,他们返回一个生成器对象,这个对象支持迭代器接口。函数也许会有个return语句,但它的作用是用来yield产生值的。

不像一般的函数会生成值后退出,生成器函数在生成值后会自动挂起并暂停他们的执行和状态,他的本地变量将保存状态信息,这些信息在函数恢复时将再度有效

1
2
3
4
5
6
7
8
>>> def g(n):
...     for i in range(n):
...             yield i **2
...
>>> for i in g(5):
...     print i,":",
...
0 : 1 : 4 : 9 : 16 :

要了解他的运行原理,我们来用next方法看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> t = g(5)
>>> t.next()
0
>>> t.next()
1
>>> t.next()
4
>>> t.next()
9
>>> t.next()
16
>>> t.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

在运行完5次next之后,生成器抛出了一个StopIteration异常,迭代终止。
再来看一个yield的例子,用生成器生成一个Fibonacci数列:

1
2
3
4
5
6
7
8
9
10
def fab(max):
    a,b = 0,1
    while a < max:
        yield a
        a, b = b, a+b
 
>>> for i in fab(20):
...     print i,",",
...
0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 ,

看到这里应该就能理解生成器那个很抽象的概念了吧~~

[php]利用curl_multi_exec和yield实现异步

Yield参照鸟哥的博客:http://www.laruence.com/2015/05/28/3038.html
这里的异步是将curl的请求发送,然后控制权交还给php,直到最后期中获取curl后的结果。程序如下:

    public function async() {
        
        $start = microtime(true);
        $url = "http://waimai.baidu.com/waimai?qt=poisug";  //随意的一个接口
        $i = 50; //假设请求N次
        do {
            $gen[$i] = $this->_request($url);
            //实际应用中可能在多次请求之间有其他事情要处理,否则我们可以直接使用curl_multi
            //suppose i can do something here
            $i --; 
        }while($i);
        
        $ret = $this->_dealGen($gen);
        var_dump($ret);
        $end = microtime(true);
        echo $end - $start;
        
    }
      
    protected function _request($url) {
        
        $mh = curl_multi_init();       
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);      
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 7);
        curl_multi_add_handle($mh, $ch);
        do {
            $mrc = curl_multi_exec($mh, $active);
			//可以看到很多参考代码这里是sleep(),其实浪费了程序执行的时间
            yield null;  //先去请求,不用等待结果
        } while ($active > 0); 
        
        $raw = curl_multi_getcontent($ch);
        curl_multi_remove_handle($mh, $ch);
        curl_multi_close($mh);
        yield $raw;       
    }
    
    protected function _dealGen($gens) {
             
        $ret = array();
        do {
            $running = false;
            foreach ($gens as $id => $gen) {              
                if ($gen->valid()) {
                    //有任意没处理完的yield就继续进行处理
                    $running = true;
                    $gen->next();
                    $tmp = $gen->current();
                    if ($tmp !== null) {
                        $ret[$id] = $tmp;
                    }
                }
            }
        } while ($running);       
        return $ret;          
    }

当然主要是接口比较给力,这里只用了0.15260910987854毫秒。可见是进行异步处理的结果。