Python中的优化:惰性求值详解

作者: 三十六_ | 来源:发表于2017-09-20 11:59 被阅读207次

惰性求值,也就是延迟求值,表达式不会在它被绑定到变量之后就立即求值,而是等用到时再求值。这个特性可以解决一些巨大甚至无限的集合列表,如菲波那切数列、几十G的文件等等。延迟求值的一个好处是能够建立可计算的无限列表而没有妨碍计算的无限循环或大小问题。

Python 中的很多方法没有直接返回列表,而是返回了一个可迭代的generator (生成器)对象,这便是python的惰性求值,因为在创建一个很大的列表时,对内存的开销非常大,太大时python会直接报错,举个🌰:range()方法是产生一个指定范围列表,在Python3之前,该方法直接产生一个列表,xrange() 产生一个生成器:

>>> xrange(100)
xrange(100)
>>> range(100)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

当参数里面的值足够大时,range()产生了一个巨大的列表,这是内存会吃不消,等待一段时间后程序会直接被Kill掉:

>>> for i in range(999999999999):
...     print i
... 
Killed: 9
占满内存

用xrange() 方法就不回出现这种问题,并且可以一直运行:

>>> for i in xrange(999999999999):
...     print i
... 
0
1
2
3
4
5
6
7
8
9
10...

在Python3中range已经被改为了xrange,所以在python3中可以放心使用range().

惰性求值不要求你事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代至某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁
还有前文所说的list comprehension语句,在两边放上[],会产生别表,如果数据源很长则会报内存错误:

>>> print [i for i in range(9999999999999999)]
Python(1627,0x7fffe5b713c0) malloc: *** mach_vm_map(size=80000000000000000) failed (error code=3)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
MemoryError

这样直接产生列表没有效率,为了创建生成器对象,可以在list comprehension两边放上(),这样它就有了惰性求值的特性。

>>> print((i for i in range(99999999999999)))
<generator object <genexpr> at 0x106e29f10>

使用next()内建函数访问生成器里的元素:

num = (i for i in range(5))
>>> num
<generator object <genexpr> at 0x106e89048>
>>> next(num)
0
>>> next(num)
1
>>> for j in range(4):
...     print(next(num))
... 
2
3
4
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
StopIteration

当访问到最后元素时,再调用next(),Python将会抛出StopIteration异常。Python正是根据是否检查到这个异常来决定是否停止迭代。

step1 = someLongOperation1()
step2 = someLongOperation2()
step3 = concatenate(step1, step2)

以上代码需要分别执行一二两步操作,第三步用到一二两步的结果,在Pyhton中会有序的执行这些函数:首先是 someLongOperation1,然后 someLongOperation2,最后 concatenate,如果确保没有函数修改或依赖于全局变量,第一二步可以被并行执行。假设我们不想并行运行这两个函数,我们只在其他函数依赖于 step1 和 step2 时才需要执行这两个函数。我们甚至在 concatenate 调用之前都不必执行他们,可以把他们的求值延迟到 concatenate 函数内实际用到他们的位置。如果函数中用到了if分支语句,条件无关step1和step2则可以尽量将判断条件放前面以减少不必要的计算:

step1 = someLongOperation1()
step2 = someLongOperation2()
if condition:
   step3 = concatenate(step1, step2)
换为:
if condition:
    step1 = someLongOperation1()
    step2 = someLongOperation2()
    step3 = concatenate(step1, step2)

如果 concatenate 是一个带有条件分支的函数并且有的分支中只用了两个参数中的一个,另一个参数就永远没有必要被求值。

相关文章

  • Python中的优化:惰性求值详解

    惰性求值,也就是延迟求值,表达式不会在它被绑定到变量之后就立即求值,而是等用到时再求值。这个特性可以解决一些巨大甚...

  • Swift Collection 中的 lazy 作用

    惰性求值 惰性求值常见于函数式编程中,也有人把惰性求值翻译成延迟求值(Lazy Evaluation)。它的目的是...

  • Swift 之惰性求值

    Swift 之惰性求值 在 Haskell 中的函数有一个特性 从而引出一个概念 惰性求值 下面有段 分析 惰性求...

  • 4.2 Variations on a Scheme: Lazy

    在元循环求值器的基础上,我们能够实现变体形式 惰性求值(lazy evaluation) 器。惰性求值器能够将程式...

  • 混入日志

    本文通过应用中天天都见得到的日志打印谈起,聊聊封装隐藏,性能优化,惰性求值,消除重复的技术实践。 延迟评估 Eli...

  • 利用 Lambda 表达式实现 Java 中的惰性求值

    Java 中惰性求值的潜能,完全被忽视了(在语言层面上,它仅被用来实现短路求值)。更先进的语言,如 Scala,区...

  • 严格求值和惰性求值(2)

    无限流与共递归因为具有增量性质,我们所写的函数也适用于无限流,这里是一个由1组成的无限流的例子: 尽管ones是无...

  • 严格求值和惰性求值(1)

    非严格求值是函数的一种属性,称一个函数是非严格求值的意思是这个函数可以选择不对它的一个或者多个参数求值;相反,严格...

  • 什么是惰性求值?

    表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值 参考 惰性求值 - 维基百科,自由的百科全书

  • 让 Python 中类的属性具有惰性求值的能力

    起步 我们希望将一个只读的属性定义为 property 属性方法,只有在访问它时才进行计算。但是,又希望把计算出的...

网友评论

本文标题:Python中的优化:惰性求值详解

本文链接:https://www.haomeiwen.com/subject/uhsnsxtx.html