-
Python 的記憶體管理
-
Copy Module
Python 的記憶體管理
參考資料:
http://billy3321.blogspot.tw/2009/01/python.html
http://www.python-course.eu/deep_copy.php
相對於 C 語言而論,Python 的層級較為高階,為了展現可讀性以及易於管理,有自動調整的功能。
按常理而言,所有的記憶體位址是不能任意更動儲存值類型的。對 Python 而言,不同類型的值進行運算時都會進行轉換,重新分配後才會寫入新的位址中,也造成執行效率沒有 C 語言來的迅速。
Python 另一個特色是 list 類型,可以存入任何類型的值,並且能自由修改與增刪。
這是因為 Python 大部分的情況下都是以「記憶體位址」來存取(可以使用 id()
函式查詢),相當於 C 語言的指標,共用指標的物件就是同一個物件,其中一項值改變時,就會一起更改。
如以下範例:
def changeLastOne(list1): list1[-1] = "c" list1.append("d") b = [1, 2, 3] changeLastOne(b) print(b) #[1, 2, "c", "d"]
上述 changeLastOne 的 function 中,「list1」存取了「b」的記憶體位址(相當於下了 list1 = b
的指令),改變「list1」的值時,「b」的值也會改變。
這種基本的參照位址不僅限於兩個變數,可以同時參照給很多對象。
a = 0 b = 0 print(a is b) #True a = 0 b = 0 a += 1 print(a is b) #False a = 1, b = 0 c = 1 print(a is c) #True
在這個例子中,Python 會將還未拿來處理的記憶體共用位址,節省空間,改變時才會複製出來修改,存入其他位址。
a = [0, 1, 2, 3, 4] b = a[:] #使用了分割器,但是不分割 b[1] = 'x' print(b) #[0, 'x', 2, 3, 4] print(a) #[0, 1, 2, 3, 4]
上述的例子拿 list a 做了分割動作給 b,不過取出了完整值,導致 Python 認為這是個「處理」動作,於是將新的記憶體位址分配給它。
a = [0, 1, 2, 3, [0, 1, 2]] b = a[:] #使用了分割器,但是不分割 b[1] = 'x' b[4][2] = 'x' print(b) #[0, 'x', 2, 3, [0, 1, 'x']] print(a) #[0, 1, 2, 3, [0, 1, 'x']]
然而對於父類型儲存的記憶體位址,Python 也會一併複製,導致兩個父類型,雖然數據不同,卻會共用同一個子類型。
Copy Module
參考資料:
https://docs.python.org/3.5/library/copy.html
不過這樣就會產生複製資料的問題,如果使用 a = b
,一律都是複製位址,無法做出一個一模一樣的資料分開修改,因此就必須匯入 Python 內建的 copy 模組。
import copy a = [0, 1, 2, 3, [0, 1, 2]] shallowCopy = copy.copy(a) deepCopy = copy.deepcopy(a)
Copy 模組中包含兩種複製方式,名為「淺層複製」和「深層複製」,可以重新命令 Python 的參照方式。
淺層複製
shallowCopy[1] = 'x' shallowCopy[4][2] = 'x' print(shallowCopy) #[0, 'x', 2, 3, [0, 1, 'x']] print(a) #[0, 1, 2, 3, [0, 1, 'x']]
相當於一般 Python 的複製方式(如同上述的分割器),底層的物件仍然是共用記憶體位址的。
深層複製
deepCopy[1] = 'x' deepCopy[4][2] = 'x' print(deepCopy) #[0, 'x', 2, 3, [0, 1, 'x']] print(a) #[0, 1, 2, 3, [0, 1, 2]]
完全的複製資料,會透過紀錄位址盡可能的將目標值搜尋回來,建立在新的記憶區中,複製出來的項目已經和原本的資料完全不一樣了。
自訂 class 的比較
from copy import copy, deepcopy class foo(): def __init__(self, mylist): self.list = mylist listA = [0, 1, 2, 3] originData = foo(listA) shallowCopy = copy(originData) deepCopy = deepcopy(originData) listA.append("g") #listA = [0, 1, 2, 3, "g"] print(shallowCopy.list) #[0, 1, 2, 3, "g"] print(deepCopy.list) #[0, 1, 2, 3]
不過進行「深層複製」會較「淺層複製」耗時間,如果想要取的值沒有子項目的話,使用「淺層複製」就不用去尋找是否有記憶體位址。
這部份紀錄是為了接下來做 Undo 與 Redo 功能,Qt 的 QUndoCommand 必須存入物件關聯,以及複製出當前的值做參考,所以必須瞭解 Python 的記憶區管理方式。
Comments
comments powered by Disqus