2011-02-09 36 views
18

我在Stack Overflow的一篇評論中讀到,在更改列表時,切片分配效率更高。例如,Python切片分配內存使用

a[:] = [i + 6 for i in a] 

應該是更多的內存效率比

a = [i + 6 for i in a] 

,因爲前者在現有列表替換元素,而後者創建一個新的列表,並重新綁定a到新名單,離開舊的a在內存中,直到它可以被垃圾收集。標杆兩個速度,後者稍快是:

$ python -mtimeit -s 'a = [1, 2, 3]' 'a[:] = [i + 6 for i in a]' 
1000000 loops, best of 3: 1.53 usec per loop 
$ python -mtimeit -s 'a = [1, 2, 3]' 'a = [i + 6 for i in a]' 
1000000 loops, best of 3: 1.37 usec per loop 

這就是我所期望的,因爲重新綁定變量應該比列表中的元素替換更快。但是,我找不到任何支持內存使用聲明的官方文檔,我不知道如何對其進行基準測試。

從表面上看,內存使用聲明對我來說很有意義。然而,給我一些更多的思考,我希望在前一種方法中,解釋器會從列表理解中創建一個新列表,然後然後將該列表中的值複製到a,從而使匿名列表浮動四處直到它是垃圾收集。如果是這樣的話,那麼前一種方法會使用相同數量的內存,同時也會變慢。

任何人都可以顯式地確定(使用基準或官方文檔)哪種方法更有效率/哪種方法是首選方法?

在此先感謝。

+1

性能方面可能值得考慮,但我認爲你更有可能遇到實際案例(在較大的程序中),其中將一個引用傳遞給列表,例如從Class1到Class2。首先,使用切片分配來修改Class1的列表將保留Class2的引用。在第二種情況中,您引用了修改Class1的列表,意味着Class2將持有對不再有效的列表的引用。 – Brandon 2011-02-09 17:59:06

+0

@Brandon:這也是事實,我可能應該在我的問題中提到這個區別。感謝您的輸入。 – 2011-02-09 18:03:36

回答

40

a[:] = [i + 6 for i in a] 

不會保存任何的記憶。 Python做第一評估的右手側,如在規定的language documentation

賦值語句計算表達式列表(記住,這可以是單個表達式或逗號分隔的列表,後者產生的元組)並從左向右分配單個結果對象到每個目標列表。

在這種情況下,單個結果對象將是一個新列表,並且目標列表中的單個目標將是a[:]

我們可以通過生成器表達式取代列表解析:

a[:] = (i + 6 for i in a) 

現在,右手邊的計算結果爲發電機,而不是一個列表。基準表明,這仍然比天真

a = [i + 6 for i in a] 

因此,沒有發電機表達真正保存任何內存慢?乍一看,你可能會認爲它確實如此。但深入到source code of the function list_ass_slice()表明它不。行

v_as_SF = PySequence_Fast(v, "can only assign an iterable"); 

使用PySequence_Fast()轉換的迭代(在這種情況下,發電機)轉換成一個元組第一,然後將其複製到舊列表。一個元組使用與列表相同數量的內存,所以在這種情況下使用生成器表達式與使用列表理解基本相同。在最後一個副本中,原始列表的項目被重新使用。

道德似乎是在任何方面最簡單的方法是最好的。