2017-06-18 53 views
4

以下代碼總結了all_numbers中列出的所有數字。這是有道理的,因爲所有要彙總的數字都保存在列表中。「元素」在哪裏存儲在生成器中?

def firstn(n): 
    '''Returns list number range from 0 to n ''' 
    num, nums = 0, [] 
    while num < n: 
     nums.append(num) 
     num += 1 
    return nums 

# all numbers are held in a list which is memory intensive 
all_numbers = firstn(100000000) 
sum_of_first_n = sum(all_numbers) 

# Uses 3.8Gb during processing and 1.9Gb to store variables 
# 13.9 seconds to process 
sum_of_first_n 

將上述函數轉換爲生成函數時,我發現使用較少的內存(下面的代碼)獲得了相同的結果。我不明白的是,如果all_numbers不包含像上面這樣的列表中的所有數字,那麼它怎樣才能被彙總呢?

如果按需生成數字,則會生成所有數字以將它們彙總在一起,因此這些數字在哪裏存儲以及這些數據如何轉化爲減少的內存使用量?

def firstn(n): 
    num = 0 
    while num < n: 
     yield num 
     num += 1 

# all numbers are held in a generator 
all_numbers = firstn(100000000) 
sum_of_first_n = sum(all_numbers) 

# Uses < 100Mb during processing and to store variables 
# 9.4 seconds to process 
sum_of_first_n 

我知道如何創建一個生成器功能,你爲什麼會想使用它們,但我不知道他們是如何工作的。

+0

您可能需要產生所有的數字...但你**不必將其全部保存在一次**,喜歡你的第一種方法 – donkopotamus

+6

他們不存儲。參見[David Beazley關於發電機的介紹](http://www.dabeaz.com/generators/)。正如其他人所說,他們不存儲在任何地方。[pdf](http://www.dabeaz.com/generators/Generators.pdf) –

+0

正如其他人所說,他們不存儲在任何地方。這是發電機的全部重點。通常你需要依次查看很多值,但是在任何時候都不需要記憶中的所有值。 – timgeb

回答

5

一個generator不存儲的值,則需要覺得發生器與上下文的功能,它在每次被要求這樣做時保存狀態和GENERATE的值,因此,它給你一個值,然後「丟棄」它,舉行計算的上下文,並等待,直到你要求更多;並且將這樣做直到世代環境耗盡

def firstn(n): 
    num = 0 
    while num < n: 
     yield num 
     num += 1 

在這個例子中,你提供,使用「唯一」內存num,是計算的存儲位置,該firstn發電機持有其context till thenum而loop`被finised。

1

這個例子可以幫助你理解當計算的項目如何和:

def firstn(n): 
    num = 0 
    while num < n: 
     yield num 
     print('incrementing num') 
     num += 1 

gen = firstn(n=10) 

a0 = next(gen) 
print(a0)  # 0 
a1 = next(gen) # incrementing num 
print(a1)  # 1 
a2 = next(gen) # incrementing num 
print(a2)  # 2 

的功能不return,但保持其內部狀態(堆棧幀),並從點它yield ED去年繼續時間。

for循環只是重複調用next

您的下一個值是按需計算的;當時並不是所有可能的值都需要在內存中。

+0

但是,'range'不是一個生成器。 –

+0

看到這個[優化[X]範圍(http://article.gmane.org/gmane.comp.python.python-3000.devel/8732)。 'range()'基本上是以相同的方式構建的。 –

+1

對不起,我不明白。你在爭論/反對什麼? –

0

如果sum -function是用Python編寫的,它很可能是與此類似:

def sum(iterable, start=0): 
    part_sum = start 
    for element in iterable: 
     part_sum += element 
    return part_sum 

(當然也有此功能,而且真正sum,但它工作的方式之間有許多差異你的例子很相似。)

如果調用sum(all_numbers)有發電機,變element只存儲當前元素和可變part_sum只存儲當前元素之前來到所有數字的總和。通過這種方式,可以僅使用兩個變量來計算總和,這顯然比存儲所有100000000個數字的數組需要更少的空間。發電機本身,正如其他人所指出的那樣,只是將其存儲當前狀態,並在與next調用了,因此只需要存儲nnum在你的例子從那裏繼續計算。

1

我想你的第一和第二函數/方法的引擎蓋下做一個真實的例子會有所幫助,你會更好地瞭解發生了什麼事情。

讓我們打印在處理使用locals()每個函數/方法是什麼Python的隱藏:

當地人():更新並返回表示當前 局部符號表的字典。當函數塊調用 時,自由變量由locals()返回,但不在類塊中調用。

>>> def firstn(n): 
    '''Returns list number range from 0 to n ''' 
    num, nums = 0, [] 
    while num < n: 
     nums.append(num) 
     num += 1 
     print(locals()) 
    return nums 
>>> firstn(10) 

會打印:

{'nums': [0], 'n': 10, 'num': 1} 
{'nums': [0, 1], 'n': 10, 'num': 2} 
{'nums': [0, 1, 2], 'n': 10, 'num': 3} 
{'nums': [0, 1, 2, 3], 'n': 10, 'num': 4} 
{'nums': [0, 1, 2, 3, 4], 'n': 10, 'num': 5} 
{'nums': [0, 1, 2, 3, 4, 5], 'n': 10, 'num': 6} 
{'nums': [0, 1, 2, 3, 4, 5, 6], 'n': 10, 'num': 7} 
{'nums': [0, 1, 2, 3, 4, 5, 6, 7], 'n': 10, 'num': 8} 
{'nums': [0, 1, 2, 3, 4, 5, 6, 7, 8], 'n': 10, 'num': 9} 
{'nums': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'n': 10, 'num': 10} 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

但是:

>>> def firstn(n): 
    num = 0 
    while num < n: 
     yield num 
     num += 1 
     print(locals()) 

>>> list(firstn(10)) 

會打印:

{'n': 10, 'num': 1} 
{'n': 10, 'num': 2} 
{'n': 10, 'num': 3} 
{'n': 10, 'num': 4} 
{'n': 10, 'num': 5} 
{'n': 10, 'num': 6} 
{'n': 10, 'num': 7} 
{'n': 10, 'num': 8} 
{'n': 10, 'num': 9} 
{'n': 10, 'num': 10} 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

所以,你可以看到第二個函數/方法(你的發電機)不關心過去或下一個過程的結果。該函數僅記住最後一個值(打破while循環的條件)並根據需要生成結果。

然而,在你的第一個例子,你的函數/方法需要存儲和記得這使得該過程很長相比,你發生器用於停止while循環然後返回最終結果的價值......一起的每一步。