2013-12-12 123 views
26

什麼是python中的懶惰評價?懶惰評價python

一個網站上說:

在Python 3.x中範圍()函數返回其計算需求(懶惰或推遲評估)列表的元素一個特殊的範圍對象:

>>> r = range(10) 
>>> print(r) 
range(0, 10) 
>>> print(r[3]) 
3 

什麼是這個意思嗎?

+1

創建一個發生器(例如包含'yield'的'def'),它會在產生一個值之前產生像print一樣的副作用。然後循環發生器,每次迭代延遲一秒。印刷何時發生? Python 3的'range'(很像Python 2中的'xrange')的工作方式就像這樣:計算直到問題才完成。這就是「懶惰評價」的意思。 – user2864740

回答

42

range()(或Python2.x中的xrange())返回的對象被稱爲generator

生成器不會將整個範圍[0,1,2,..,9]存儲在內存中,而是存儲(i=0; i<10; i+=1)的定義,並僅在需要時計算下一個值(AKA懶惰評估)。

從本質上講,一臺發電機允許返回狀結構的列表,但這裏有一些差異:

  1. 列表存儲在創建時的所有元素。發生器在需要時生成下一個元素。
  2. 可以根據需要迭代列表,一個生成器只能在之間迭代,一次只能迭代一次。
  3. 一個列表可以通過索引獲取元素,一個生成器不能 - 它只能從頭到尾生成一次值。

    (1)非常相似,列表理解:

    # this is a list, create all 5000000 x/2 values immediately, uses [] 
    lis = [x/2 for x in range(5000000)] 
    
    # this is a generator, creates each x/2 value only when it is needed, uses() 
    gen = (x/2 for x in range(5000000)) 
    

    (2)作爲一個功能,採用yield返回

的發電機可以用兩種方法來創建下一個值:

# this is also a generator, it will run until a yield occurs, and return that result. 
# on the next call it picks up where it left off and continues until a yield occurs... 
def divby2(n): 
    num = 0 
    while num < n: 
     yield num/2 
     num += 1 

# same as (x/2 for x in range(5000000)) 
print divby2(5000000) 

注意:儘管range(5000000)是Python3.x中的生成器,但[x/2 for x in range(5000000)]仍然是一個列表。 range(...)它的工作是否正常並且每次生成一個x,但是在創建此列表時將計算整個x/2值的列表。

+0

這是一個很好的詳細答案,bcorso。 :) +1 – zx81

+3

實際上,'range'(或2.x中的'xrange')不會*返回一個生成器。一個生成器是一個迭代器 - 對於任何生成器'g'你都可以調用'next(g)'。範圍對象實際上是可迭代的。你可以調用'iter'來得到一個迭代器,但它本身不是一個迭代器(你不能調用'next')。除此之外,這意味着您可以多次迭代單個範圍對象。 –

7

簡而言之,懶惰評估意味着對象在需要時進行評估,而不是在創建時進行評估。

在Python 2,範圍將返回一個列表 - 這意味着,如果你給它一個大的數字,它會計算的範圍,並在創建的時候返回:但是

>>> i = range(100) 
>>> type(i) 
<type 'list'> 

在Python 3,你會得到一個特殊的範圍對象:

>>> i = range(100) 
>>> type(i) 
<class 'range'> 

只有當你使用它,將它實際上進行評估 - 換句話說,它只會在返回範圍內的數字,當你真正需要它們。

+0

感謝您定義「懶惰」:) – BoltzmannBrain

0

一個名爲python patternswikipedia的github回購告訴我們什麼是懶惰評估。

推遲expr的eval直到它的值被需要,並避免重複的evals。

range python3不是一個完整的懶惰評估,因爲它不能避免重複評估。

更典型的例子爲惰性計算是cached_property

import functools 

class cached_property(object): 
    def __init__(self, function): 
     self.function = function 
     functools.update_wrapper(self, function) 

    def __get__(self, obj, type_): 
     if obj is None: 
      return self 
     val = self.function(obj) 
     obj.__dict__[self.function.__name__] = val 
     return val 

的cached_property(a.k.a lazy_property)是轉換FUNC成惰性計算特性的裝飾。第一次訪問屬性時,調用func來獲取結果,然後在下次訪問屬性時使用該值。

例如:

class LogHandler: 
    def __init__(self, file_path): 
     self.file_path = file_path 

    @cached_property 
    def load_log_file(self): 
     with open(self.file_path) as f: 
      # the file is to big that I have to cost 2s to read all file 
      return f.read() 

log_handler = LogHandler('./sys.log') 
# only the first time call will cost 2s. 
print(log_handler.load_log_file) 
# return value is cached to the log_handler obj. 
print(log_handler.load_log_file) 

要使用適當的字,一個python發生器對象像範圍更像通過設計call_by_need圖案,而不是惰性計算