0

Python Shallow vs deep copy: copy bản sao mà bản gốc cũng đổi

Shallow vs deep copy: copy bản sao mà bản gốc cũng đổi

Bài 4 — series "Python cho dân backend".

Bài 2 nói b = a không copy. Vậy gọi .copy() cho chắc là xong chớ gì? Đố nè:

goc = {"ten": "A", "tags": ["x", "y"]}
ban_sao = goc.copy()          # tưởng đã có bản riêng độc lập
ban_sao["tags"].append("z")
print(goc["tags"])            # ['x', 'y', 'z'] — bản GỐC cũng đổi (!)

Bây sửa ban_saogoc dính theo. .copy() rõ ràng có copy mà, sao vẫn lủng? Vì có hai tầng copy, và bây mới làm tầng ngoài.

Dùng AI coding agent? Khi bây nhờ AI tạo hàm sao chép config hay xử lý dữ liệu lồng nhau, nó hay tự tay viết .copy() — gọn, trông đúng, chạy được. Nó sẽ không hỏi bây "cần độc lập tới tầng nào". Kết quả: data nhiễm chéo giữa các request, lần bug cả buổi mà không thấy. Hiểu shallow/deep thì bây đọc code AI xong biết ngay cần đổi chỗ nào.

Đa số nghĩ: .copy() cho bản sao độc lập hoàn toàn

Chỉ đúng một nửa. Có hai loại:

Shallow copy (.copy(), copy.copy(), dict(d), list(l), l[:]):

  • Tạo container mới ở tầng ngoài.
  • Nhưng các phần tử bên trong vẫn là tham chiếu tới CÙNG object (nhớ bài 2!).

Nên trong cái đố: ban_sao là dict mới, nhưng ban_sao["tags"]goc["tags"] vẫn trỏ chung một list. Append vào list đó → cả hai thấy.

Deep copy (copy.deepcopy()):

  • Sao đệ quy cả các object lồng bên trong → độc lập hoàn toàn.
import copy
goc = {"ten": "A", "tags": ["x", "y"]}
ban_sao = copy.deepcopy(goc)
ban_sao["tags"].append("z")
print(goc["tags"])            # ['x', 'y'] — gốc KHÔNG đổi

Cơ chế: shallow chỉ copy "lớp vỏ"

Hình dung dữ liệu lồng nhau như hộp trong hộp. Shallow copy làm một cái hộp ngoài mới, nhưng các hộp con bên trong thì dùng chung với bản gốc. Deep copy làm mới toàn bộ cây hộp.

Với dữ liệu phẳng (chỉ chứa số/chuỗi immutable), shallow là đủ độc lập — vì immutable không sửa tại chỗ được (bài 2). Vấn đề chỉ phát sinh khi có mutable lồng trong mutable (list trong dict, dict trong list...).

Hệ quả & khi nào dùng cái nào

Bug điển hình: copy một config/JSON-like nhiều tầng, sửa bản sao, rồi bản gốc (hoặc bản sao khác) cũng đổi — dữ liệu nhiễm chéo, khó lần.

  • Dữ liệu phẳng / không lồng mutable → shallow copy là đủ, nhanh.
  • Dữ liệu lồng nhau cần độc lập thậtdeepcopy. Nhưng nó chậm và tốn RAM (sao cả cây), đừng deepcopy bừa object lớn trong vòng nóng.
  • Tốt nhứt khi được: thiết kế để không phải copy — dùng dữ liệu immutable (tuple, frozenset), hoặc tạo mới thay vì sửa-rồi-chia-sẻ. Tránh được chia sẻ thì khỏi lo copy.

Checklist: bây nắm chưa?

  • [ ] Shallow copy copy tới tầng nào? (Chỉ container ngoài; phần tử bên trong vẫn dùng chung.)
  • [ ] Vì sao sửa ban_sao["tags"]goc đổi? (Hai dict khác nhau nhưng cùng trỏ chung một list tags.)
  • [ ] Deep copy khác gì? (Sao đệ quy cả object lồng → độc lập hoàn toàn.)
  • [ ] Dữ liệu phẳng (chỉ số/chuỗi) có cần deepcopy hông? (Hổng — shallow đủ, vì immutable.)
  • [ ] Nhược của deepcopy? (Chậm, tốn RAM — đừng dùng bừa object lớn.)
  • [ ] Cách né cả vấn đề copy? (Dùng dữ liệu immutable / tránh chia sẻ mutable.)

Bài tới: "Scope LEGB + closure — vì sao [lambda: i for i in range(3)] cả ba đều trả 2."


Bản gốc đăng tại Substack: https://quakebaynghe.substack.com/p/python-shallow-vs-deep-copy-copy


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí