Copy trong Python: Sao chép hay 'chép' nhầm? Gen Z phải biết!
Python

Copy trong Python: Sao chép hay 'chép' nhầm? Gen Z phải biết!

Author

Admin System

@root

Ngày xuất bản

22 Mar, 2026

Lượt xem

1 Lượt

"copy"

Chào các "thánh code" tương lai của Anh Creyt! Hôm nay, mình sẽ "bóc phốt" một khái niệm tưởng chừng đơn giản nhưng lại hay khiến các bạn "toang" không ít: Copy trong Python. Nghe có vẻ "ez game" nhưng tin anh đi, nó ẩn chứa cả một "vũ trụ" khác đấy!

1. "Copy" là gì mà Gen Z phải quan tâm?

Thường thì, khi muốn có một bản sao của cái gì đó, các bạn hay làm thế này đúng không?

list_goc = [1, 2, 3]
list_moi = list_goc # Ơ, tưởng copy rồi?
list_moi.append(4)
print(list_goc) # Output: [1, 2, 3, 4] -- Ủa, tại sao lại thay đổi cả list_goc?

Đây chính là "chép nhầm" chứ không phải "sao chép" đâu nha! Trong Python, khi bạn gán list_moi = list_goc, bạn không tạo ra một bản sao mới. Bạn chỉ đang tạo ra một cái tên (biến) khác để cùng chỉ vào cùng một đối tượng trong bộ nhớ. Giống như bạn và "crush" của bạn cùng gọi một người bạn thân là "bồ tèo" vậy. Dù có hai cái tên, nhưng đó vẫn là một người duy nhất thôi.

Vậy nên, khi bạn thay đổi list_moi, bạn đang thay đổi cái đối tượng mà cả list_goclist_moi cùng trỏ tới. Kết quả là list_goc cũng bị "ảnh hưởng" theo.

Để thực sự tạo ra một bản sao ĐỘC LẬP, chúng ta cần đến hai khái niệm "xịn xò" hơn:

2. Shallow Copy (Sao chép nông): "Bản photo nhanh gọn"

Shallow copy giống như bạn đi photocopy một tài liệu vậy. Bạn có một bản giấy mới, nhưng nếu trong tài liệu gốc có những "ghi chú" (ví dụ: một tờ giấy nhớ dán vào), thì bản photo của tờ giấy nhớ đó vẫn đang trỏ về cái tờ giấy nhớ gốc đó. Nếu bạn sửa đổi nội dung trên tờ giấy nhớ gốc, thì cả bản gốc và bản photo của bạn đều thấy sự thay đổi đó.

Nói cách khác, shallow copy tạo ra một đối tượng mới, nhưng nếu đối tượng gốc chứa các đối tượng con (như list trong list, dictionary trong list), thì bản sao mới sẽ chỉ chứa các tham chiếu (pointers) đến chính các đối tượng con đó, chứ không tạo bản sao của chúng.

Khi nào dùng? Khi đối tượng của bạn chỉ chứa các kiểu dữ liệu "bất biến" (immutable) như số, chuỗi, tuple; hoặc khi bạn OK với việc các đối tượng con "có thể" bị chia sẻ giữa bản gốc và bản sao.

Cách thực hiện Shallow Copy:

  • Với List/Tuple/Set: Dùng slicing [:], hàm list(), tuple(), set().

    list_goc = [1, 2, [3, 4]]
    list_shallow_copy = list_goc[:] # Hoặc list(list_goc)
    list_shallow_copy[0] = 100 # Thay đổi phần tử bất biến
    list_shallow_copy[2].append(5) # Thay đổi phần tử mutable (list con)
    
    print(f"List gốc: {list_goc}") # Output: List gốc: [1, 2, [3, 4, 5]]
    print(f"Shallow Copy: {list_shallow_copy}") # Output: Shallow Copy: [100, 2, [3, 4, 5]]
    # Thấy chưa? list_goc cũng bị thay đổi ở phần tử con!
    
  • Với Dictionary: Dùng hàm dict() hoặc phương thức .copy().

    dict_goc = {'a': 1, 'b': {'c': 2}}
    dict_shallow_copy = dict_goc.copy() # Hoặc dict(dict_goc)
    dict_shallow_copy['a'] = 100
    dict_shallow_copy['b']['c'] = 200 # Thay đổi phần tử mutable (dict con)
    
    print(f"Dict gốc: {dict_goc}") # Output: Dict gốc: {'a': 1, 'b': {'c': 200}}
    print(f"Shallow Copy: {dict_shallow_copy}") # Output: Shallow Copy: {'a': 100, 'b': {'c': 200}}
    # Lại một pha "đi vào lòng đất" của dict_goc!
    
  • Dùng module copy: Đây là cách "chính chủ" và rõ ràng nhất.

    import copy
    
    list_goc = [1, 2, [3, 4]]
    list_shallow_copy_module = copy.copy(list_goc)
    
    list_shallow_copy_module[2].append(5)
    print(f"List gốc (qua module): {list_goc}") # Output: List gốc (qua module): [1, 2, [3, 4, 5]]
    print(f"Shallow Copy (qua module): {list_shallow_copy_module}") # Output: Shallow Copy (qua module): [1, 2, [3, 4, 5]]
    
Illustration

3. Deep Copy (Sao chép sâu): "Bản sao y bản chính, độc lập hoàn toàn"

Deep copy thì "chất chơi" hơn nhiều. Nó giống như bạn không chỉ photo tài liệu, mà còn tỉ mỉ chép lại TẤT CẢ các ghi chú trên tờ giấy nhớ đó vào một tờ giấy nhớ mới, rồi dán vào bản photo mới của bạn. Từ giờ, hai bản tài liệu hoàn toàn độc lập. Bạn sửa gì trên bản gốc thì bản photo không hề hay biết, và ngược lại.

Deep copy tạo ra một đối tượng mới và đệ quy (recursively) tạo bản sao của tất cả các đối tượng con bên trong, cho đến khi không còn đối tượng con nào để sao chép nữa. Kết quả là bạn có một bản sao hoàn toàn độc lập, không "dây mơ rễ má" gì với bản gốc cả.

Khi nào dùng? Khi bạn cần một bản sao hoàn toàn độc lập, đặc biệt là khi đối tượng của bạn có chứa các đối tượng con là kiểu dữ liệu "có thể thay đổi" (mutable) như list, dict, set, hoặc các instance của class.

Cách thực hiện Deep Copy:

  • Luôn phải dùng module copy và hàm deepcopy().
    import copy
    
    list_goc = [1, 2, [3, 4]]
    list_deep_copy = copy.deepcopy(list_goc)
    
    list_deep_copy[0] = 100
    list_deep_copy[2].append(5) # Thay đổi phần tử mutable (list con)
    
    print(f"List gốc: {list_goc}") # Output: List gốc: [1, 2, [3, 4]]
    print(f"Deep Copy: {list_deep_copy}") # Output: Deep Copy: [100, 2, [3, 4, 5]]
    # Aha! List gốc vẫn "bình yên vô sự"! Độc lập hoàn toàn!
    

4. Mẹo (Best Practices) từ "lão làng" Creyt

  1. Hiểu rõ "mutable" và "immutable": Đây là "chìa khóa" để hiểu copy. Các kiểu dữ liệu immutable (số, chuỗi, tuple) khi thay đổi sẽ tạo ra đối tượng mới. Các kiểu mutable (list, dict, set) có thể thay đổi ngay trên đối tượng hiện có. Khi đối tượng gốc chỉ chứa immutable, shallow copy và deep copy sẽ "giống nhau" về mặt hành vi với các phần tử cấp 1.
  2. "When in doubt, deepcopy it out!": Nếu bạn không chắc chắn và cần sự an toàn tuyệt đối, cứ dùng deepcopy(). Nó sẽ đảm bảo bản sao của bạn hoàn toàn độc lập. Tuy nhiên, deep copy tốn nhiều tài nguyên hơn (thời gian và bộ nhớ) vì nó phải duyệt qua tất cả các cấp độ.
  3. Visual hóa: Hãy tưởng tượng các biến như những "nhãn dán" và đối tượng là "hộp quà". Gán = là dán thêm nhãn. Shallow copy là tạo hộp quà mới nhưng bên trong vẫn dùng chung đồ chơi. Deep copy là tạo hộp quà mới và mua đồ chơi mới y hệt bỏ vào.
  4. Kiểm tra id: Dùng id() để xem các biến có đang trỏ đến cùng một đối tượng trong bộ nhớ hay không. id(obj1) == id(obj2) nghĩa là chúng là cùng một đối tượng.

5. Ứng dụng thực tế: "Copy" có mặt ở đâu?

  • Game Development: Khi bạn muốn lưu trạng thái game (save game), bạn cần deep copy toàn bộ trạng thái hiện tại của game để tạo một bản lưu độc lập. Nếu không, khi bạn tiếp tục chơi và thay đổi gì đó, bản save cũ cũng "toang" theo.
  • Undo/Redo Functionality: Các ứng dụng chỉnh sửa ảnh, văn bản cần deepcopy trạng thái trước đó để có thể hoàn tác (undo) hoặc làm lại (redo) một cách chính xác mà không ảnh hưởng đến trạng thái hiện tại.
  • Machine Learning/Data Science: Khi bạn làm việc với các tập dữ liệu phức tạp (ví dụ: DataFrame trong Pandas), bạn thường cần tạo các bản sao độc lập để thử nghiệm các thuật toán khác nhau mà không làm hỏng dữ liệu gốc.
  • Quản lý cấu hình (Configuration Management): Một số hệ thống cần giữ lại cấu hình mặc định (default config) và cho phép người dùng tùy chỉnh. Để tránh việc tùy chỉnh làm thay đổi cấu hình gốc, bạn sẽ cần shallow hoặc deep copy tùy vào độ phức tạp của cấu hình.

6. Thử nghiệm và Nên dùng cho case nào?

Anh Creyt đã từng "ngã sấp mặt" nhiều lần với vụ copy này hồi mới vào nghề. Anh cứ nghĩ gán là copy, đến lúc debug code thấy dữ liệu "nhảy múa" lung tung mới vỡ lẽ. Vậy nên, kinh nghiệm xương máu là:

  • Dùng = (gán): Khi bạn chỉ muốn có thêm một tên gọi khác cho cùng một đối tượng. Ví dụ, truyền một list vào hàm và muốn hàm đó thao tác trực tiếp trên list gốc.
  • Dùng Shallow Copy: Khi đối tượng của bạn là "đơn giản" (chỉ chứa các kiểu immutable) hoặc khi bạn chấp nhận được việc các đối tượng con (nếu có) bị chia sẻ. Ví dụ, bạn có một list các số, và bạn chỉ muốn tạo một list mới với các số đó.
  • Dùng Deep Copy: Đây là "vũ khí tối thượng" khi bạn cần sự độc lập hoàn toàn. Khi bạn có các cấu trúc dữ liệu lồng nhau phức tạp (list chứa dict, dict chứa object của class khác), và bạn muốn mọi thay đổi trên bản sao KHÔNG BAO GIỜ ảnh hưởng đến bản gốc. Đây là lựa chọn an toàn nhất, dù tốn kém hơn một chút.

Nhớ nhé, hiểu rõ "copy" là một trong những bước đầu tiên để trở thành một "dev xịn xò", tránh được những lỗi "lãng xẹt" mà đến khi tìm ra nguyên nhân thì chỉ muốn "độn thổ" thôi! Cứ thử nghiệm, và nếu có "toang" thì hỏi anh Creyt nhé!

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!