• 从range和xrange的性能对比到yield关键字(中)


    上节提出了range和xrange的效率问题,这节我们来探究其中的原因

    yield的使用

    我们看下面的程序:

    #coding: utf-8
    
    def test():
        print 4
        print 2
        print 5
    
    if __name__ == '__main__':
        test()

    这段代码的运行结果当然是没有任何疑问的。

    但是如果我将代码修改一下:

    #coding: utf-8
    
    def test():
        yield 4
        yield 2
        yield 5
    
    if __name__ == '__main__':
        print test()

    运行结果有些奇怪:

    <generator object test at 0xb71f1144>

    我们尝试这样使用:

    if __name__ == '__main__':
        for i in test():
            print i

    结果却出人意料:

    wing@ubuntu:~/Documents/py|⇒  python 17.py
    4
    2
    5

    这是什么原因呢?这里看起来,test()好像一个集合,里面存储了4,2,5,所以我们才能够依次遍历。

    实际上,原因并非如此。

    当一个函数中含有yield时,这个函数就不再是一个普通的函数,而是一个可迭代的对象(实际上叫做生成器,不过现在不必关心概念)。

    同样,执行该函数时,不再是马上执行其中的语句,而是生成一个可迭代对象。当执行迭代的时候,才真正运行其中的代码。

    当函数体执行到yield时,便退出这个函数,此时yield具有return的功能。但是这里的关键是,当下次执行这个函数时,并不是从头开始执行,而是从上次yield退出的位置继续执行

    尝试下面的代码:

    #coding: utf-8
    
    def test():
        yield 4
        yield 2
        yield 5
    
    if __name__ == '__main__':
        t = test()
        it = iter(t)
        print it.next()
        print it.next()
        print it.next()
        print it.next()

    运行结果为:

    wing@ubuntu:~/Documents/py|⇒  python 17.py
    4
    2
    5
    Traceback (most recent call last):
      File "17.py", line 14, in <module>
        print it.next()
    StopIteration

    从这里的结果可以看出,test()语句没有执行代码段,而是生成了一个可以迭代的对象。

    我们甚至可以得出结论,每当执行一次next,就向后执行到下一个yield语句,或者所有的语句执行完毕。

    range的实现

    我们尝试实现range:

    #coding: utf-8
    
    def _range(value):
        i = 0
        result = []
        while i < value:
            result.append(i)
            i += 1
        return result
    
    if __name__ == '__main__':
        for i in _range(4):
            print i

    range的逻辑比较简单,就是生成一个列表。

    xrange的模拟实现

    我们根据前面的结论,猜测xrange是一个含有yield的函数,于是:

    #coding: utf-8
    
    def _xrange(value):
        i = 0
        while i < value:
            yield i
            i += 1
    
    if __name__ == '__main__':
        for i in _xrange(4):
            print i

    运行一下,结果和我们预期一致。

    当然,实际的xrange比我们这里编写的更加复杂,但是基本原理是一致的。

    为何xrange比range高效?

    答案很明显了,range是一次性生成所有的数据,而xrange,内部使用了yield关键字,每次只运行其中一部分,这样从头到尾都没有占用大量的内存和时间。所以效率较高。

    我们再次比较性能,这次比较的是我们自己编写的版本:

    #coding: utf-8
    import sys
    from time import time
    
    def _range(value):
        i = 0
        result = []
        while i < value:
            result.append(i)
            i += 1
        return result
    
    def _xrange(value):
        i = 0
        while i < value:
            yield i
            i += 1
    
    def count_time(func):
        def wrapped(*args, **kargs):
            begin_time = time()
            result = func(*args, **kargs)
            end_time = time()
            cost_time = end_time - begin_time
            print '%s called cost time : %s ms' %(func.__name__, float(cost_time)*1000)
            return result
        return wrapped
    
    @count_time
    def test1(length):
        for i in _range(length):
            pass
    
    @count_time
    def test2(length):
        for i in _xrange(length):
            pass
    
    if __name__ == '__main__':
        length = int(sys.argv[1])
        test1(length)
        test2(length)

    运行结果为:

    wing@ubuntu:~/Documents/py|⇒  python 19.py 1000
    test1 called cost time : 0.116109848022 ms
    test2 called cost time : 0.0619888305664 ms
    wing@ubuntu:~/Documents/py|⇒  python 19.py 10000
    test1 called cost time : 2.39086151123 ms
    test2 called cost time : 0.566959381104 ms
    wing@ubuntu:~/Documents/py|⇒  python 19.py 100000
    test1 called cost time : 15.5799388885 ms
    test2 called cost time : 6.41298294067 ms
    wing@ubuntu:~/Documents/py|⇒  python 19.py 1000000
    test1 called cost time : 130.295038223 ms
    test2 called cost time : 65.4468536377 ms
    wing@ubuntu:~/Documents/py|⇒  python 19.py 10000000
    test1 called cost time : 13238.3038998 ms
    test2 called cost time : 652.212142944 ms

    显然,使用yield的版本更加高效。

    下文,我们探究生成器。

  • 相关阅读:
    如何在centos上安装epel源
    Linux基础命令之cat使用方法大全
    Python正则表达式一: 基本使用方法
    Python 2.7 学习笔记 中文处理
    python 中 json的处理
    centos下网络代理服务器的配置
    centos7 在 vmware下的安装与配置
    Linux学习:find、chmod、ps命令
    Linux学习:netstat命令
    Linux学习:curl 与 wget命令
  • 原文地址:https://www.cnblogs.com/inevermore/p/4224985.html
Copyright © 2020-2023  润新知