Copy & Deepcopy: Giải mã bản sao 'ảo' và 'thật' trong Python
Python

Copy & Deepcopy: Giải mã bản sao 'ảo' và 'thật' trong Python

Author

Admin System

@root

Ngày xuất bản

22 Mar, 2026

Lượt xem

1 Lượt

"copy_deepcopy"

Chào các 'dev-er' tương lai! Anh Creyt lại lên sóng với một chủ đề mà nghe thì tưởng đơn giản, nhưng lại là cái bẫy 'ngọt ngào' khiến bao nhiêu anh tài phải 'vò đầu bứt tai' khi mới vào nghề: copydeepcopy trong Python. Nghe tên là thấy mùi sao chép rồi đúng không? Nhưng tin anh đi, không phải cái 'copy-paste' thần thánh mà các em hay dùng đâu!

1. copydeepcopy là gì? (Hay: Khi nào dữ liệu của em 'nhân bản vô tính'?)

Để dễ hình dung, các em cứ tưởng tượng thế này:

a. Tham Chiếu (Reference) - 'Chỉ đường' chứ không 'nhân bản'

Trước khi nói đến copydeepcopy, anh em mình phải hiểu cái này trước đã. Khi các em gán b = a trong Python (với a là một đối tượng có thể thay đổi như list hay dict), các em không hề tạo ra một bản sao của a đâu nhé. Thực ra, các em chỉ đang tạo ra một cái tên khác (b) cùng trỏ vào một 'địa chỉ nhà' (object) trong bộ nhớ với cái tên a mà thôi.

Giống như việc các em và bạn cùng lưu một đường link Google Drive về bức ảnh 'tự sướng' của cả nhóm. Nếu một đứa vào chỉnh sửa ảnh gốc trên Drive, thì đứa kia mở lên cũng thấy bản đã sửa. Đó là tham chiếu!

list_goc = [1, 2, 3]
list_tham_chieu = list_goc # list_tham_chieu và list_goc cùng trỏ về 1 đối tượng

list_tham_chieu.append(4)
print(f"List gốc sau khi sửa: {list_goc}") # Output: [1, 2, 3, 4] - A hú!
print(f"List tham chiếu: {list_tham_chieu}") # Output: [1, 2, 3, 4]

b. copy (Shallow Copy) - 'Sao chép nông' hay 'Nhân bản cấp 1'

Bây giờ đến copy. Cái này giống như các em chụp ảnh màn hình một bài post trên Instagram. Các em có một bản sao của bài post đó trên máy mình. Các em có thể chỉnh sửa, vẽ vời lên bản ảnh chụp màn hình đó mà không ảnh hưởng đến bài post gốc trên Instagram của đứa bạn.

NHƯNG! Nếu bài post đó lại là một album ảnh (tức là cấu trúc lồng nhau), và các em chụp ảnh màn hình cả album. Các em có thể đổi tên album đã chụp, di chuyển nó, nhưng các bức ảnh CON bên TRONG album đó thì sao? Chúng vẫn là CÙNG MỘT BỨC ẢNH với các bức ảnh con trong album gốc. Nếu đứa bạn sửa một bức ảnh trong album gốc, thì bức ảnh đó trong album đã chụp màn hình của các em cũng... thay đổi theo! Hơi 'lú' đúng không?

Đó chính là shallow copy (sao chép nông). Nó tạo ra một đối tượng MỚI cho cấu trúc chính (list, dict, set), nhưng với các phần tử CON bên trong (nếu các phần tử đó là các đối tượng có thể thay đổi như list, dict khác), nó vẫn giữ nguyên các tham chiếu đến các đối tượng con gốc. Tức là, thay đổi các đối tượng con trong bản sao sẽ ảnh hưởng đến các đối tượng con trong bản gốc.

Khi nào dùng? Khi các em chỉ cần một bản sao của cấu trúc cấp độ đầu tiên, và các phần tử bên trong là các kiểu dữ liệu bất biến (số, chuỗi, tuple không chứa mutable) hoặc các em chấp nhận việc chúng vẫn chia sẻ tham chiếu.

c. deepcopy (Deep Copy) - 'Sao chép sâu' hay 'Nhân bản vô tính toàn diện'

Đây mới là 'vô tính' thực sự! deepcopy giống như việc các em tải nguyên cả một bộ phim về máy tính cá nhân. Các em có một bản sao hoàn toàn độc lập, từ cái vỏ bên ngoài đến từng khung hình, từng pixel bên trong. Các em có thể cắt ghép, chỉnh sửa, xóa cảnh nào đó trong bản phim của mình mà không một chút tẹo nào ảnh hưởng đến bản gốc của nhà sản xuất phim.

deepcopy tạo ra một đối tượng MỚIđệ quy tạo ra các đối tượng MỚI cho tất cả các phần tử con, cháu, chắt... bên trong nó. Tức là, mọi thứ đều độc lập hoàn toàn. Chỉnh sửa bản sao không bao giờ ảnh hưởng đến bản gốc, và ngược lại. Đây là 'sao chép toàn diện'.

Khi nào dùng? Khi các em cần một bản sao hoàn toàn độc lập, không có bất kỳ sự chia sẻ tham chiếu nào với đối tượng gốc, đặc biệt với các đối tượng chứa các đối tượng có thể thay đổi (mutable objects) lồng nhau.

2. Code Ví Dụ Minh Họa Đàng Hoàng

Để 'sáng mắt' hơn, chúng ta cùng xem vài ví dụ code nhé. Nhớ import copy khi dùng deepcopy!

import copy

print("\n--- Ví dụ 1: Đối tượng đơn giản (Mutable) ---")
list_goc = [1, 2, 3]

# Gán (Reference)
list_tham_chieu = list_goc
list_tham_chieu.append(4)
print(f"Gán (Reference) - List gốc: {list_goc}, List tham chiếu: {list_tham_chieu}") # Cả hai đều thay đổi

# Shallow Copy (list.copy() hoặc list() hoặc list[:])
list_goc_2 = [10, 20, 30]
list_shallow = list_goc_2.copy()
list_shallow.append(40)
print(f"Shallow Copy - List gốc: {list_goc_2}, List shallow: {list_shallow}") # List gốc không thay đổi

# Deep Copy (Không cần thiết lắm với list đơn giản này, nhưng để so sánh)
list_goc_3 = [100, 200, 300]
list_deep = copy.deepcopy(list_goc_3)
list_deep.append(400)
print(f"Deep Copy - List gốc: {list_goc_3}, List deep: {list_deep}") # List gốc không thay đổi

print("\n--- Ví dụ 2: Đối tượng lồng nhau (List of Lists) ---")
list_lon_goc = [[1, 2], [3, 4]]

# Gán (Reference)
list_lon_tham_chieu = list_lon_goc
list_lon_tham_chieu[0].append(5) # Sửa phần tử con
print(f"Gán (Reference) - List lồng gốc: {list_lon_goc}, List lồng tham chiếu: {list_lon_tham_chieu}")
# Output: [[1, 2, 5], [3, 4]], [[1, 2, 5], [3, 4]] -> Cả hai đều thay đổi

print("\n--- Ví dụ 3: Shallow Copy với đối tượng lồng nhau ---")
list_lon_goc_2 = [[10, 20], [30, 40]]
list_lon_shallow = list_lon_goc_2.copy() # Tạo bản sao của list ngoài
list_lon_shallow.append([50, 60]) # Thêm phần tử mới vào list ngoài của bản sao -> Không ảnh hưởng gốc
list_lon_shallow[0].append(25) # Sửa phần tử con của list ngoài bản sao -> ẢNH HƯỞNG GỐC!
print(f"Shallow Copy - List lồng gốc: {list_lon_goc_2}")
# Output: [[10, 20, 25], [30, 40]] -> Phần tử con trong gốc bị sửa!
print(f"Shallow Copy - List lồng shallow: {list_lon_shallow}")
# Output: [[10, 20, 25], [30, 40], [50, 60]]

print("\n--- Ví dụ 4: Deep Copy với đối tượng lồng nhau ---")
list_lon_goc_3 = [['A', 'B'], ['C', 'D']]
list_lon_deep = copy.deepcopy(list_lon_goc_3) # Tạo bản sao hoàn toàn độc lập
list_lon_deep.append(['E', 'F'])
list_lon_deep[0].append('X') # Sửa phần tử con của list ngoài bản sao -> KHÔNG ẢNH HƯỞNG GỐC!
print(f"Deep Copy - List lồng gốc: {list_lon_goc_3}")
# Output: [['A', 'B'], ['C', 'D']] -> Hoàn toàn nguyên vẹn!
print(f"Deep Copy - List lồng deep: {list_lon_deep}")
# Output: [['A', 'B', 'X'], ['C', 'D'], ['E', 'F']]
Illustration

3. Mẹo (Best Practices) để ghi nhớ và dùng thực tế

  • Quy tắc 'Ngón Tay Cái' (Rule of Thumb):
    • Nếu đối tượng của em chỉ chứa các phần tử 'bất biến' (immutable) như số, chuỗi, tuple (mà tuple đó không chứa các đối tượng mutable bên trong), thì copy hay deepcopy đều cho kết quả như nhau (và thường chỉ cần copy hoặc thậm chí gán rồi tạo đối tượng mới cho mutable là đủ).
    • Nếu đối tượng của em chứa các phần tử 'có thể thay đổi' (mutable) như list, dict, set, hoặc các custom objects khác, thì hãy CẨN THẬN! copy chỉ sao chép cấp độ đầu tiên. Nếu em muốn tất cả các cấp đều độc lập hoàn toàn, không liên quan gì đến nhau, thì DÙNG deepcopy.
  • Hiệu suất là vấn đề: deepcopy tốn tài nguyên (CPU và bộ nhớ) hơn copy rất nhiều vì nó phải 'lặn sâu' vào từng ngóc ngách của đối tượng để tạo bản sao. Chỉ dùng khi thực sự cần sự độc lập hoàn toàn. Đừng lạm dụng!
  • Nhớ import copy: Muốn dùng deepcopy, đừng quên dòng import copy ở đầu file nhé.
  • Hình dung bằng sơ đồ cây: Hãy tưởng tượng đối tượng của em là một cái cây. copy giống như việc em chặt cái cây đó ở gốc và trồng nó vào một chậu mới, nhưng các cành con (đối tượng con) trên cây đó vẫn là cành cũ, vẫn dính vào nhau. Còn deepcopy là em nhổ cả rễ, sao chép cả cây lẫn từng chiếc lá, từng cái rễ con, rồi trồng một cây hoàn toàn mới, không liên quan gì đến cây cũ nữa.

4. Ứng dụng thực tế: Khi nào thì 'nhân bản vô tính' phát huy tác dụng?

copydeepcopy không phải là những thứ 'trên trời rơi xuống' đâu, chúng được ứng dụng rất nhiều trong các hệ thống thực tế:

  • Phát triển Game: Khi người chơi lưu game (save game), hệ thống cần tạo một bản sao hoàn toàn độc lập của trạng thái game hiện tại (vị trí nhân vật, vật phẩm, nhiệm vụ...). Nếu chỉ dùng copy mà trạng thái game có cấu trúc lồng nhau, khi người chơi tiếp tục chơi và thay đổi trạng thái, bản save game cũng bị thay đổi theo, và đó là một 'bug' cực kỳ khó chịu!
  • Chức năng Undo/Redo: Trong các trình chỉnh sửa văn bản, ảnh (như Photoshop), video... mỗi khi các em thực hiện một thao tác, hệ thống cần lưu lại trạng thái trước đó để các em có thể 'undo'. Để đảm bảo việc 'undo' không làm hỏng trạng thái hiện tại hoặc các trạng thái đã lưu khác, việc tạo một deepcopy của trạng thái là cực kỳ quan trọng.
  • Quản lý cấu hình (Configuration Management): Một ứng dụng có thể đọc một file cấu hình và tạo ra một đối tượng cấu hình. Nếu các module khác nhau trong ứng dụng cần tùy chỉnh cấu hình này cho riêng mình mà không muốn ảnh hưởng đến cấu hình gốc hoặc cấu hình của các module khác, họ sẽ cần một deepcopy của đối tượng cấu hình đó.
  • Machine Learning/AI: Khi huấn luyện các mô hình, các nhà khoa học dữ liệu có thể muốn thử nghiệm nhiều bộ tham số khác nhau trên cùng một tập dữ liệu hoặc trên một bản sao của mô hình mà không làm hỏng bản gốc, để có thể so sánh và quay lại các phiên bản trước đó.

5. Thử nghiệm và Hướng dẫn nên dùng cho case nào

Anh Creyt ngày xưa cũng đã từng 'ăn hành' vì nhầm lẫn giữa gán và copy đấy các em. Nhất là khi làm việc với cấu trúc dữ liệu lồng nhau, cứ tưởng copy rồi mà sửa chỗ này nó lại ảnh hưởng chỗ kia, ngáo ngơ cả buổi không hiểu tại sao. Đó là bài học xương máu về shallow copy!

Khi nào nên dùng copy (Shallow Copy)?

  • Khi đối tượng của em chỉ chứa các phần tử immutable (số, chuỗi, tuple không chứa mutable). Trong trường hợp này, copy hay deepcopy đều cho kết quả tương tự về mặt chức năng, và copy sẽ hiệu quả hơn.
  • Khi em chỉ cần sao chép cấp độ đầu tiên của một đối tượng mutable (ví dụ: một list các số), và em không quan tâm đến việc các phần tử bên trong có thể vẫn chia sẻ tham chiếu (vì chúng là immutable hoặc em có chủ đích muốn vậy).
  • Khi em cần hiệu suất tốt hơn và không có cấu trúc lồng nhau mutable.

Khi nào nên dùng deepcopy (Deep Copy)?

  • Khi đối tượng của em là một cấu trúc lồng nhau phức tạp (list of lists, dict of lists, custom objects chứa các đối tượng khác). Đây là lúc deepcopy thực sự tỏa sáng.
  • Khi em cần đảm bảo rằng bản sao hoàn toàn độc lập với bản gốc, mọi thay đổi trên bản sao sẽ không bao giờ ảnh hưởng đến bản gốc, và ngược lại.
  • Khi em đang làm việc với các hệ thống cần sự 'tách biệt' hoàn toàn giữa các phiên bản dữ liệu (như các ví dụ về game saves, undo/redo, snapshot hệ thống).

Nhớ kỹ nhé các em, hiểu rõ copydeepcopy không chỉ giúp các em tránh được những 'bug' khó nhằn mà còn thể hiện sự chuyên nghiệp và tư duy lập trình vững chắc. Cứ thực hành nhiều vào, rồi các em sẽ 'master' được thôi!

Thuộc Series: Python

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!