1. Python 的記憶體管理

  2. 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