Quản lý bộ nhớ (Memory Management)
Python tự động quản lý bộ nhớ thông qua cơ chế Reference Counting và Garbage Collection. Hiểu cách Python quản lý bộ nhớ giúp bạn viết code hiệu quả hơn và tránh memory leaks.
1. Cách Python lưu trữ dữ liệu
Phần tiêu đề “1. Cách Python lưu trữ dữ liệu”Everything is an Object
Phần tiêu đề “Everything is an Object”Trong Python, mọi thứ đều là object - kể cả số, chuỗi, hàm, class.
# Tất cả đều là objectsnumber = 42text = "Hello"my_list = [1, 2, 3]
# Kiểm tra ID của object (địa chỉ bộ nhớ)print(id(number)) # Ví dụ: 140705844567328print(id(text)) # Ví dụ: 2234567891234print(id(my_list)) # Ví dụ: 2234567898765
# Mỗi object có typeprint(type(number)) # <class 'int'>print(type(text)) # <class 'str'>print(type(my_list)) # <class 'list'>Biến là References (tham chiếu)
Phần tiêu đề “Biến là References (tham chiếu)”Biến trong Python không chứa giá trị trực tiếp, mà chứa tham chiếu (reference) đến object.
# 'a' tham chiếu đến object số 100a = 100
# 'b' tham chiếu đến cùng object với 'a'b = a
print(id(a)) # 140705844567328print(id(b)) # 140705844567328 (cùng ID)print(a is b) # True (cùng object)
# Gán lại 'a' - tạo tham chiếu mớia = 200print(id(a)) # 140705844567360 (ID khác)print(id(b)) # 140705844567328 (vẫn giữ ID cũ)print(a is b) # FalseMinh họa với List (Mutable Object)
Phần tiêu đề “Minh họa với List (Mutable Object)”# list1 tham chiếu đến một list objectlist1 = [1, 2, 3]print(id(list1)) # Ví dụ: 2234567898765
# list2 tham chiếu đến CÙNG object với list1list2 = list1print(id(list2)) # 2234567898765 (cùng ID)
# Thay đổi qua list2list2.append(4)
# list1 cũng thay đổi (vì cùng object)print(list1) # [1, 2, 3, 4]print(list2) # [1, 2, 3, 4]
# Tạo list mới (copy)list3 = list1.copy()print(id(list3)) # ID khác (object mới)
list3.append(5)print(list1) # [1, 2, 3, 4] (không đổi)print(list3) # [1, 2, 3, 4, 5]2. Reference Counting (Đếm tham chiếu)
Phần tiêu đề “2. Reference Counting (Đếm tham chiếu)”Khái niệm
Phần tiêu đề “Khái niệm”Python theo dõi số lượng tham chiếu đến mỗi object. Khi số tham chiếu về 0, object được giải phóng.
import sys
# Tạo objecta = [1, 2, 3]print(sys.getrefcount(a)) # 2 (1 từ 'a', 1 từ getrefcount)
# Tạo thêm tham chiếub = aprint(sys.getrefcount(a)) # 3 (a, b, getrefcount)
c = aprint(sys.getrefcount(a)) # 4 (a, b, c, getrefcount)
# Xóa tham chiếudel bprint(sys.getrefcount(a)) # 3 (a, c, getrefcount)
del cprint(sys.getrefcount(a)) # 2 (a, getrefcount)
# Khi del a, reference count = 0 → object bị xóaVí dụ thực tế
Phần tiêu đề “Ví dụ thực tế”import sys
# Object với 1 referencex = "Hello"print(sys.getrefcount(x)) # Có thể > 2 (Python cache chuỗi)
# Object trong containermy_list = [1, 2, 3]container = [my_list, my_list, my_list]print(sys.getrefcount(my_list)) # 5 (my_list, 3 refs trong container, getrefcount)
# Xóa containerdel containerprint(sys.getrefcount(my_list)) # 2 (chỉ còn my_list và getrefcount)3. Garbage Collection (Thu gom rác)
Phần tiêu đề “3. Garbage Collection (Thu gom rác)”Reference Counting không đủ
Phần tiêu đề “Reference Counting không đủ”Reference counting không xử lý được circular references (tham chiếu vòng).
import sys
class Node: def __init__(self, value): self.value = value self.next = None
# Tạo circular referencenode1 = Node(1)node2 = Node(2)
node1.next = node2 # node1 → node2node2.next = node1 # node2 → node1 (vòng!)
print(sys.getrefcount(node1)) # 3 (node1, node2.next, getrefcount)print(sys.getrefcount(node2)) # 3 (node2, node1.next, getrefcount)
# Xóa node1 và node2del node1del node2
# Reference count vẫn > 0 (vì tham chiếu lẫn nhau)# → Reference counting KHÔNG giải phóng được# → Cần Garbage CollectorGarbage Collector tự động
Phần tiêu đề “Garbage Collector tự động”Python có bộ Garbage Collector tự động phát hiện và xóa circular references.
import gc
# Xem cấu hình garbage collectorprint(gc.get_threshold()) # (700, 10, 10)# 700: số object mới trước khi chạy generation 0 collection# 10: tần suất collection cho generation 1# 10: tần suất collection cho generation 2
# Xem số lượng collections đã chạyprint(gc.get_count()) # (số object, số collections gen0, gen1, gen2)
# Chạy garbage collection thủ côngcollected = gc.collect()print(f"Collected {collected} objects")
# Kiểm tra GC có bật khôngprint(gc.isenabled()) # True
# Tắt GC (KHÔNG KHUYẾN KHÍCH)# gc.disable()
# Bật lại GC# gc.enable()Generations trong Garbage Collector
Phần tiêu đề “Generations trong Garbage Collector”Python GC sử dụng 3 generations (thế hệ):
import gc
# Generation 0: Objects mới tạo# Generation 1: Objects sống sót qua collection gen 0# Generation 2: Objects sống sót qua collection gen 1
# Xem thống kêstats = gc.get_stats()for i, stat in enumerate(stats): print(f"Generation {i}: {stat}")
# Ví dụ output:# Generation 0: {'collections': 45, 'collected': 1234, 'uncollectable': 0}# Generation 1: {'collections': 4, 'collected': 56, 'uncollectable': 0}# Generation 2: {'collections': 0, 'collected': 0, 'uncollectable': 0}4. Mutable vs Immutable Objects
Phần tiêu đề “4. Mutable vs Immutable Objects”Immutable Objects (Không thay đổi được)
Phần tiêu đề “Immutable Objects (Không thay đổi được)”Immutable objects không thể thay đổi sau khi tạo. Python tối ưu bằng cách tái sử dụng object.
# Số nguyên nhỏ (-5 đến 256) được cachea = 10b = 10print(a is b) # True (cùng object)print(id(a) == id(b)) # True
# Số lớn hơn không được cache (có thể khác nhau)x = 1000y = 1000print(x is y) # False (Python 3.8+: có thể True trong cùng dòng)print(id(x) == id(y)) # False
# Chuỗi ngắn được interned (cache)s1 = "hello"s2 = "hello"print(s1 is s2) # True (cùng object)
# Chuỗi có ký tự đặc biệt có thể không được interneds3 = "hello world!"s4 = "hello world!"print(s3 is s4) # Có thể False (tùy Python version)
# Tuple là immutablet = (1, 2, 3)# t[0] = 10 # Lỗi: 'tuple' object does not support item assignmentMutable Objects (Thay đổi được)
Phần tiêu đề “Mutable Objects (Thay đổi được)”Mutable objects có thể thay đổi mà không tạo object mới.
# List là mutablelist1 = [1, 2, 3]id_before = id(list1)
list1.append(4) # Thay đổi object hiện tạiid_after = id(list1)
print(id_before == id_after) # True (vẫn là object cũ)
# Dictionary là mutabledict1 = {"a": 1}id_before = id(dict1)
dict1["b"] = 2id_after = id(dict1)
print(id_before == id_after) # True
# Set là mutableset1 = {1, 2, 3}id_before = id(set1)
set1.add(4)id_after = id(set1)
print(id_before == id_after) # TrueẢnh hưởng đến Memory
Phần tiêu đề “Ảnh hưởng đến Memory”# Immutable: Tạo object mới → tốn memory nếu thay đổi nhiềus = "a"for i in range(5): s += "a" # Tạo string mới mỗi lần print(id(s)) # ID khác nhau mỗi lần
# Mutable: Thay đổi object hiện tại → tiết kiệm memorylst = ["a"]id_original = id(lst)for i in range(5): lst.append("a") # Không tạo object mới print(id(lst) == id_original) # True mọi lần5. Copy vs Deep Copy
Phần tiêu đề “5. Copy vs Deep Copy”Shallow Copy (Copy nông)
Phần tiêu đề “Shallow Copy (Copy nông)”Copy nông tạo object mới nhưng không copy các object bên trong.
import copy
# List lồng nhauoriginal = [[1, 2], [3, 4]]
# Shallow copyshallow = copy.copy(original)# Hoặc: shallow = original.copy()# Hoặc: shallow = original[:]
print(id(original)) # ID khácprint(id(shallow)) # ID khác
# Nhưng các list bên trong vẫn là cùng objectprint(id(original[0])) # ID giốngprint(id(shallow[0])) # ID giốngprint(original[0] is shallow[0]) # True
# Thay đổi list conshallow[0].append(5)
print(original) # [[1, 2, 5], [3, 4]] - BỊ THAY ĐỔI!print(shallow) # [[1, 2, 5], [3, 4]]Deep Copy (Copy sâu)
Phần tiêu đề “Deep Copy (Copy sâu)”Deep copy tạo object mới và copy đệ quy tất cả objects bên trong.
import copy
# List lồng nhauoriginal = [[1, 2], [3, 4]]
# Deep copydeep = copy.deepcopy(original)
print(id(original)) # ID khácprint(id(deep)) # ID khác
# Các list bên trong cũng khác nhauprint(id(original[0])) # ID khácprint(id(deep[0])) # ID khácprint(original[0] is deep[0]) # False
# Thay đổi list condeep[0].append(5)
print(original) # [[1, 2], [3, 4]] - KHÔNG THAY ĐỔIprint(deep) # [[1, 2, 5], [3, 4]]Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”import copy
class Person: def __init__(self, name, hobbies): self.name = name self.hobbies = hobbies # list
# Originalalice = Person("Alice", ["reading", "coding"])
# Shallow copyalice_shallow = copy.copy(alice)alice_shallow.name = "Alice Clone" # OK - string immutablealice_shallow.hobbies.append("gaming") # ẢNH HƯỞNG alice!
print(alice.name) # Alice (không đổi)print(alice.hobbies) # ['reading', 'coding', 'gaming'] - BỊ THAY ĐỔI!
# Deep copybob = Person("Bob", ["swimming", "running"])bob_deep = copy.deepcopy(bob)bob_deep.name = "Bob Clone"bob_deep.hobbies.append("cycling") # KHÔNG ảnh hưởng bob
print(bob.name) # Bob (không đổi)print(bob.hobbies) # ['swimming', 'running'] - KHÔNG THAY ĐỔIprint(bob_deep.hobbies) # ['swimming', 'running', 'cycling']6. Memory Profiling
Phần tiêu đề “6. Memory Profiling”Đo kích thước object
Phần tiêu đề “Đo kích thước object”import sys
# Kích thước của objectprint(sys.getsizeof(1)) # 28 bytes (int)print(sys.getsizeof("a")) # 50 bytes (str)print(sys.getsizeof([])) # 56 bytes (empty list)print(sys.getsizeof([1, 2, 3])) # 80 bytes (list with 3 items)print(sys.getsizeof({})) # 64 bytes (empty dict)
# Chú ý: getsizeof() chỉ đo kích thước object,# KHÔNG bao gồm objects mà nó tham chiếu đến
my_list = [[1, 2, 3], [4, 5, 6]]print(sys.getsizeof(my_list)) # Chỉ đo list ngoài# Không tính kích thước của [1, 2, 3] và [4, 5, 6]Đo tổng kích thước (bao gồm nested objects)
Phần tiêu đề “Đo tổng kích thước (bao gồm nested objects)”import sys
def get_size(obj, seen=None): """Tính tổng kích thước bao gồm cả nested objects""" size = sys.getsizeof(obj) if seen is None: seen = set()
obj_id = id(obj) if obj_id in seen: return 0
seen.add(obj_id)
if isinstance(obj, dict): size += sum([get_size(v, seen) for v in obj.values()]) size += sum([get_size(k, seen) for k in obj.keys()]) elif hasattr(obj, '__dict__'): size += get_size(obj.__dict__, seen) elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes, bytearray)): size += sum([get_size(i, seen) for i in obj])
return size
# Testdata = { 'numbers': [1, 2, 3, 4, 5], 'nested': { 'list': [10, 20, 30], 'dict': {'a': 1, 'b': 2} }}
print(f"Shallow size: {sys.getsizeof(data)} bytes")print(f"Total size: {get_size(data)} bytes")7. Best Practices
Phần tiêu đề “7. Best Practices”✅ NÊN làm
Phần tiêu đề “✅ NÊN làm”# 1. Xóa biến không dùng nữa (giải phóng memory)big_list = list(range(1000000))# ... xử lý xongdel big_list
# 2. Dùng generator thay vì list (tiết kiệm memory)# ❌ Tốn memorysquares_list = [x**2 for x in range(1000000)]
# ✅ Tiết kiệm memorysquares_gen = (x**2 for x in range(1000000))
# 3. Đọc file lớn từng dòng# ❌ Tốn memorywith open('large_file.txt') as f: lines = f.readlines() # Đọc hết vào memory
# ✅ Tiết kiệm memorywith open('large_file.txt') as f: for line in f: # Đọc từng dòng process(line)
# 4. Dùng __slots__ cho class nhiều instancesclass Point: __slots__ = ['x', 'y'] # Giảm memory consumption
def __init__(self, x, y): self.x = x self.y = y
# 5. Clear cache khi không cầnimport functools
@functools.lru_cache(maxsize=128)def expensive_function(n): return n ** 2
# Khi không cần cache nữaexpensive_function.cache_clear()❌ KHÔNG NÊN làm
Phần tiêu đề “❌ KHÔNG NÊN làm”# 1. Tạo list lớn không cần thiết# ❌result = []for i in range(1000000): result.append(i * 2)
# ✅ Dùng generatordef generate_results(): for i in range(1000000): yield i * 2
# 2. Circular references không cần thiết# ❌class Node: def __init__(self, value): self.value = value self.parent = None self.children = []
# Cẩn thận với parent/children references
# 3. Giữ references đến objects lớn# ❌cache = {}def process_data(data): cache[id(data)] = data # Giữ reference → không giải phóng # ...8. Bài tập thực hành
Phần tiêu đề “8. Bài tập thực hành”Bài 1: So sánh shallow vs deep copy
Phần tiêu đề “Bài 1: So sánh shallow vs deep copy”import copy
original = { 'name': 'John', 'scores': [85, 90, 92], 'address': { 'city': 'NYC', 'zipcode': '10001' }}
shallow = copy.copy(original)deep = copy.deepcopy(original)
# Thay đổi scoresshallow['scores'].append(95)
print("Original:", original['scores'])print("Shallow:", shallow['scores'])print("Deep:", deep['scores'])
# Kết quả:# Original: [85, 90, 92, 95] - BỊ THAY ĐỔI# Shallow: [85, 90, 92, 95]# Deep: [85, 90, 92] - KHÔNG THAY ĐỔIBài 2: Memory-efficient data processing
Phần tiêu đề “Bài 2: Memory-efficient data processing”import sys
# ❌ Không hiệu quảdef process_numbers_list(n): numbers = [x for x in range(n)] squares = [x**2 for x in numbers] return sum(squares)
# ✅ Hiệu quả hơndef process_numbers_gen(n): return sum(x**2 for x in range(n))
n = 1000000
# So sánh memorylist_result = process_numbers_list(n)gen_result = process_numbers_gen(n)
print(f"Results equal: {list_result == gen_result}")Tổng kết
Phần tiêu đề “Tổng kết”- Reference Counting: Python đếm số tham chiếu đến mỗi object
- Garbage Collection: Tự động phát hiện và xóa circular references
- Immutable objects: int, float, str, tuple, frozenset - không thay đổi được
- Mutable objects: list, dict, set - có thể thay đổi
- Shallow copy: Copy object nhưng không copy nested objects
- Deep copy: Copy đệ quy tất cả objects
- Best practices:
- Xóa biến không dùng với
del - Dùng generators thay vì lists khi xử lý dữ liệu lớn
- Cẩn thận với circular references
- Sử dụng
__slots__cho classes có nhiều instances - Đọc file lớn từng dòng thay vì đọc hết vào memory
- Xóa biến không dùng với