Vì sao gán b = a lại làm đổi luôn cả a? Gốc rễ mô hình Object trong Python
Gán không copy: vì sao b = a rồi sửa b mà a cũng đổi
Bài 2 - series "Python cho dân backend: nền tảng để lên level".
Mở màn bằng một câu đố như lần trước nghen:
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] - ủa, tao có đụng a đâu?
Bây gán b = a, sửa b, mà a cũng dính. Tưởng b = a là sao chép một bản riêng cho b -> trật. Hiểu cái này là hiểu gốc rễ của nửa số bug "tự nhiên dữ liệu bị đổi" trong Python.

Đa số nghĩ: biến là cái hộp chứa giá trị, gán là copy
Trật lất nghe bây. Trong Python:
Biến là cái NHÃN (tên) gắn vào một object, không phải cái hộp chứa giá trị.
b = achỉ dán thêm nhãnbvào cùng object màađang trỏ tới.
Nên a và b là hai cái nhãn trên một cái list. Sửa cái list qua nhãn b thì nhãn a "thấy" liền - vì có một cái list thôi. Kiểm bằng id(a) == id(b) -> True (cùng identity).
Mutable vs immutable - chia hai thế giới
Hình dung cho dễ:
- Mutable (list, dict, set) giống cái rổ - bỏ thêm trái cây vô thì vẫn là cái rổ cũ, chỉ là trong rổ có thêm đồ. Sửa-tại-chỗ được, nên mọi nhãn trỏ tới nó đều thấy thay đổi. Đây là chỗ gây bất ngờ.
- Immutable (int, str, tuple, bool, None) giống số khắc trên đá - muốn số khác phải lấy cục đá khác. Không sửa-tại-chỗ được: "sửa" thực ra là tạo object mới rồi dời nhãn sang đó -> nhãn kia không hề hấn:
a = 5
b = a
b = b + 1 # b trỏ object mới (6); a vẫn 5
print(a, b) # 5 6
a += b khác a = a + b (với list)
Cái này hay làm người ta vấp:
a = [1, 2]
b = a
a += [3] # SỬA TẠI CHỖ list cũ (như extend) -> b cũng thấy
print(b) # [1, 2, 3]
a = [1, 2]
b = a
a = a + [3] # TẠO list mới, dời nhãn a sang đó -> b giữ list cũ
print(b) # [1, 2]
+= trên mutable là sửa tại chỗ; a = a + b là tạo mới.
Truyền vào hàm: "pass by object reference"
Python không pass-by-value như Go, cũng không pass-by-reference kiểu C++ - nó truyền tham chiếu tới object (gọi là pass-by-assignment). Hệ quả thực tế:
def sua(lst):
lst.append(99) # SỬA object -> bên ngoài THẤY
def gan_lai(lst):
lst = [0] # GÁN LẠI tên cục bộ -> bên ngoài KHÔNG thấy
x = [1, 2]
sua(x); print(x) # [1, 2, 99]
gan_lai(x); print(x) # [1, 2, 99] (không đổi - chỉ dời nhãn cục bộ)
Sửa nội dung object thì bên ngoài thấy; gán lại cái tên (tham số) thì chỉ đổi nhãn cục bộ trong hàm, bên ngoài không hề hấn.
Hệ quả & liên hệ Go
Đây là nguồn của loại bug "dữ liệu bị đổi mà không biết ai đổi". Muốn b độc lập với a thì phải copy thật (bài sau) - mà copy cũng có bẫy shallow/deep.
Liên hệ Go (series Golang, bài 1): Go copy value khi truyền tham số; Python truyền tham chiếu tới object. Hai ngôn ngữ ngược nhau, mà cùng một bài học sống còn: luôn biết cái gì đang được chia sẻ, cái gì là bản riêng.

Checklist: bây nắm chưa?
- Biến trong Python là hộp giá trị hay nhãn trỏ object? (Nhãn trỏ object.)
b = acó copy hông? (Hông à nha - chỉ thêm nhãn vào cùng object.)- Mutable vs immutable khác gì khi gán/sửa? (Mutable = rổ, sửa-tại-chỗ -> mọi nhãn đều thấy; immutable = số khắc đá, "sửa" là tạo mới -> dời nhãn.)
a += [3]kháca = a + [3]chỗ nào? (+=sửa tại chỗ list cũ;a = a + ...tạo list mới.)- Sửa object trong hàm, bên ngoài có thấy hông? Gán lại tên thì sao? (Sửa object: thấy; gán lại tên: không.)
- Kiểm hai nhãn cùng object bằng gì? (
id()/is.)
Bài tới: "is vs == - và vì sao 256 is 256 cho True mà 257 is 257 có thể False."
Bản gốc đăng tại Substack: https://quakebaynghe.substack.com/p/python-object-model-mutable-immutable
All rights reserved