1. 什麼是物件的記憶體位置?
在 Python 中,每個物件在記憶體中都有一個唯一的位置,這個位置可以用 id()
函式查詢。
這個 id
在 CPython(最常見的 Python 實作)中,實際上就是物件的記憶體地址。
a = [1, 2, 3]
print(id(a)) # 印出物件 a 的記憶體位置
print(hex(id(a))) # 轉成 16 進位,更像記憶體位址
2. 物件的記憶體位置何時會改變?
(1) 同一個物件,id 不會變
只要你沒有重新指派,物件的 id 就不會變:a = [1, 2, 3]
print(id(a)) # 例如:140123456789456
a.append(4)
print(id(a)) # 還是同一個 id
(2) 重新賦值,id 會變
如果你把變數指向新的物件,id 就會改變:
a = [1, 2, 3]
print(id(a)) # 140123456789456
a = [4, 5, 6] # a 指向新的 list
print(id(a)) # 另一個 id,例如 140123456789888
(3) 多個變數指向同一個物件
a = [1, 2, 3]
b = a
print(id(a), id(b)) # 兩個 id 一樣
3. 變數、物件、記憶體的關係
- 變數:只是物件的「名字」或「參照」。
- 物件:真正存在記憶體中的資料。
- id():查詢物件的記憶體位置(地址)。
圖解:
a ──► [1, 2, 3] (物件在 0x7fffaabbccdd)
b ──┘
4. 重新調用(重新賦值)時的記憶體刷新
當你把變數指向新物件(例如 a = [4, 5, 6]
),
- 原本的物件如果沒其他變數參照,就會被 Python 的垃圾回收機制釋放。
- 變數
a
會指向新物件,id(a)
也會變。
5. 不同型態的物件行為
(1) 不可變物件(immutable)
如 int
、str
、tuple
,每次改變內容都會產生新物件:
x = 10
print(id(x))
x += 1
print(id(x)) # id 變了
(2) 可變物件(mutable)
如 list
、dict
,內容改變但 id 不變:
lst = [1, 2]
print(id(lst))
lst.append(3)
print(id(lst)) # id 不變
6. NumPy 陣列的記憶體位置
NumPy 陣列有兩個重點:
id(arr)
:Python 物件的記憶體位置arr.ctypes.data
:底層資料的記憶體位置
import numpy as np
arr = np.zeros((3, 3), dtype=np.uint8)
print(id(arr)) # Python 物件 id
print(hex(arr.ctypes.data)) # 資料起始位址
7. 記憶體釋放與垃圾回收
(1) 只要還有參照,記憶體就不會釋放
- Python 會自動管理記憶體。只要有變數參照著一個物件,這個物件就會一直存在於記憶體中。
- 即使你不再使用這個物件,只要有變數還指向它,記憶體就不會被釋放。
a = [1, 2, 3]
# 只要 a 還在,這個 list 就會一直佔用記憶體
(2) 沒有參照時,才會自動釋放(垃圾回收)
- 當沒有任何變數參照這個物件時,Python 的垃圾回收機制(Garbage Collector, GC)會自動釋放這塊記憶體。
- 你可以用
del
刪除變數,或讓變數指向其他物件,原本的物件如果沒被其他變數參照,就會被回收。
a = [1, 2, 3]
b = a
del a # b 還在,所以記憶體不會釋放
del b # 沒有參照了,list 會被釋放
(3) 程式結束時,所有記憶體都會被釋放
- 當 Python 程式結束時,所有物件都會自動釋放,不用手動清理。
8. 常見誤區
- 誤區 1:以為不用的物件會自動釋放。
只要還有變數參照,記憶體就不會釋放。 - 誤區 2:用
del
就能馬上釋放記憶體。
只有當所有參照都消失,記憶體才會被釋放。
9. 小結
- 物件的記憶體會一直被占用,只要有變數參照它。
- 沒有參照時,Python 會自動釋放記憶體。
- 程式結束時,所有記憶體都會自動釋放。
10. 範例說明
import sys
a = [1, 2, 3]
b = a
print(sys.getsizeof(a)) # 查看記憶體用量
del a
# b 還在,記憶體還沒釋放
del b
# 現在沒有參照,list 會被釋放