Mô-đun `copy` của Python
Bài viết này giải thích mô-đun copy của Python.
Tập trung vào sự khác biệt giữa sao chép nông và sao chép sâu, chúng tôi cung cấp lời giải thích rõ ràng—từ các cơ chế cơ bản của việc nhân bản đối tượng đến ứng dụng trong các lớp tùy chỉnh—kèm các ví dụ thực tế.
YouTube Video
Mô-đun copy của Python
Mô-đun copy của Python là mô-đun tiêu chuẩn để xử lý việc nhân bản (sao chép) đối tượng. Mục tiêu là hiểu sự khác biệt giữa bản sao nông và bản sao sâu và có thể kiểm soát hành vi sao chép cho các đối tượng tùy chỉnh.
Cơ bản: Sao chép nông là gì?
Ở đây chúng tôi minh họa hành vi của bản sao nông đối với các đối tượng có thể thay đổi như danh sách (list) và từ điển (dict). Bản sao nông chỉ nhân đôi đối tượng cấp trên cùng và chia sẻ các tham chiếu bên trong.
1# Example: shallow copy with lists and dicts
2import copy
3
4# A nested list
5original = [1, [2, 3], 4]
6shallow = copy.copy(original)
7
8# Mutate nested element
9shallow[1].append(99)
10
11print("original:", original) # nested change visible in original
12print("shallow: ", shallow)Trong đoạn mã này, danh sách ở cấp trên cùng được sao chép, nhưng các danh sách con bên trong được chia sẻ theo tham chiếu, vì vậy shallow[1].append(99) cũng được phản ánh trong original. Đây là hành vi điển hình của bản sao nông.
Sao chép sâu là gì?
Bản sao sâu sao chép đệ quy đối tượng và tất cả các đối tượng được tham chiếu bên trong, tạo ra một đối tượng độc lập. Dùng khi bạn muốn nhân bản an toàn các cấu trúc lồng nhau phức tạp.
1# Example: deep copy with nested structures
2import copy
3
4original = [1, [2, 3], {'a': [4, 5]}]
5deep = copy.deepcopy(original)
6
7# Mutate nested element of the deep copy
8deep[1].append(100)
9deep[2]['a'].append(200)
10
11print("original:", original) # original remains unchanged
12print("deep: ", deep)Trong ví dụ này, thay đổi ở deep không ảnh hưởng tới original. deepcopy sao chép toàn bộ đồ thị đối tượng, tạo ra một bản sao độc lập.
Cách quyết định nên dùng loại nào
Nên dùng kiểu sao chép nào phụ thuộc vào cấu trúc của đối tượng và mục đích của bạn. Nếu bạn không muốn các thay đổi đối với các phần tử bên trong có thể thay đổi ảnh hưởng tới đối tượng gốc, deepcopy là phù hợp. Bởi vì nó sao chép đệ quy toàn bộ đối tượng, tạo ra một bản sao hoàn toàn độc lập.
Ngược lại, nếu chỉ nhân đôi đối tượng cấp trên cùng là đủ và bạn coi trọng tốc độ và hiệu quả bộ nhớ, copy (bản sao nông) phù hợp hơn.
- Bạn muốn tránh việc đối tượng gốc thay đổi khi chỉnh sửa các phần tử bên trong có thể thay đổi → dùng
deepcopy. - Chỉ cần nhân đôi cấp trên cùng và bạn muốn ưu tiên hiệu năng (tốc độ/bộ nhớ) → dùng
copy(sao chép nông).
Xử lý các đối tượng tích hợp sẵn và bất biến
Đối tượng bất biến (ví dụ: int, str và tuple có nội dung bất biến) thường không cần được sao chép. copy.copy có thể trả về cùng một đối tượng khi phù hợp.
1# Example: copying immutables
2import copy
3
4a = 42
5b = copy.copy(a)
6print(a is b) # True for small ints (implementation detail)
7
8s = "hello"
9t = copy.deepcopy(s)
10print(s is t) # True (strings are immutable)Vì sao chép các đối tượng bất biến hầu như không mang lại lợi ích hiệu năng, cùng một đối tượng có thể được tái sử dụng. Điều này hiếm khi là thứ bạn cần bận tâm trong thiết kế ứng dụng.
Lớp tùy chỉnh: định nghĩa __copy__ và __deepcopy__
Nếu hành vi sao chép mặc định không như bạn mong đợi cho lớp của mình, bạn có thể cung cấp logic sao chép riêng. Nếu bạn định nghĩa __copy__ và __deepcopy__, copy.copy và copy.deepcopy sẽ sử dụng chúng.
1# Example: customizing copy behavior
2import copy
3
4class Node:
5 def __init__(self, value, children=None):
6 self.value = value
7 self.children = children or []
8
9 def __repr__(self):
10 return f"Node({self.value!r}, children={self.children!r})"
11
12 def __copy__(self):
13 # Shallow copy: create new Node, but reuse children list reference
14 new = self.__class__(self.value, self.children)
15 return new
16
17 def __deepcopy__(self, memo):
18 # Deep copy: copy value and deepcopy each child
19 new_children = [copy.deepcopy(child, memo) for child in self.children]
20 new = self.__class__(copy.deepcopy(self.value, memo), new_children)
21 memo[id(self)] = new
22 return new- Bằng cách triển khai
__copy__và__deepcopy__, bạn có thể linh hoạt kiểm soát hành vi sao chép đặc thù của lớp. Ví dụ, bạn có thể xử lý các trường hợp trong đó một số thuộc tính nên tham chiếu (chia sẻ) cùng một đối tượng, trong khi các thuộc tính khác nên được sao chép thành các đối tượng hoàn toàn mới. - Đối số
memođược truyền vào__deepcopy__là một từ điển ghi lại các đối tượng đã được xử lý trong quá trình sao chép đệ quy và ngăn chặn vòng lặp vô hạn do các tham chiếu vòng gây ra.
1# Build tree
2root_node = Node('root', [Node('child1'), Node('child2')])
3
4shallow_copy = copy.copy(root_node) # shallow copy
5deep_copy = copy.deepcopy(root_node) # deep copy
6
7# Modify children
8# affects both root_node and shallow_copy
9shallow_copy.children.append(Node('child_shallow'))
10# affects deep_copy only
11deep_copy.children.append(Node('child_deep'))
12
13# Print results
14print("root_node:", root_node)
15print("shallow_copy:", shallow_copy)
16print("deep_copy:", deep_copy)- Trong đoạn mã này, biến
shallow_copyđược tạo thông qua 'bản sao nông', nên chỉ cấp trên cùng của đối tượng được sao chép, còn các đối tượng mà nó tham chiếu bên trong (trong trường hợp này là danh sáchchildren) được dùng chung vớiroot_nodegốc. Do đó, khi bạn thêm một nút mới vàoshallow_copy, danh sáchchildrenđược chia sẻ sẽ được cập nhật và thay đổi này cũng được phản ánh trong nội dung củaroot_node. - Ngược lại, biến
deep_copyđược tạo thông qua 'bản sao sâu', vì vậy cấu trúc bên trong được sao chép đệ quy, tạo ra một cây hoàn toàn độc lập vớiroot_node. Vì vậy, ngay cả khi bạn thêm một nút mới vàodeep_copy, điều đó cũng không ảnh hưởng đếnroot_node.
Tham chiếu vòng (đối tượng đệ quy) và tầm quan trọng của memo
Khi một đồ thị đối tượng phức tạp tự tham chiếu (tham chiếu vòng), deepcopy dùng một từ điển memo để theo dõi các đối tượng đã được sao chép và ngăn vòng lặp vô hạn.
1# Example: recursive list and deepcopy memo demonstration
2import copy
3
4a = []
5b = [a]
6a.append(b) # a -> [b], b -> [a] (cycle)
7
8# deepcopy can handle cycles
9deep = copy.deepcopy(a)
10print("deep copy succeeded, length:", len(deep))- Bên trong,
deepcopydùngmemođể tham chiếu tới các đối tượng đã được nhân bản, cho phép sao chép an toàn ngay cả khi có các chu trình. Bạn cần một cơ chế tương tự khi tự triển khai đệ quy.
copyreg và sao chép tùy chỉnh theo kiểu tuần tự hóa (nâng cao)
Nếu bạn muốn đăng ký hành vi sao chép đặc biệt phối hợp với các thư viện, bạn có thể dùng mô-đun chuẩn copyreg để đăng ký cách đối tượng được (tái) tạo. Điều này hữu ích cho các đối tượng phức tạp và các kiểu mở rộng C.
1# Example: using copyreg to customize pickling/copy behavior (brief example)
2import copy
3import copyreg
4
5class Wrapper:
6 def __init__(self, data):
7 self.data = data
8
9def reduce_wrapper(obj):
10 # Return callable and args so object can be reconstructed
11 return (Wrapper, (obj.data,))
12
13copyreg.pickle(Wrapper, reduce_wrapper)
14
15w = Wrapper([1, 2, 3])
16w_copy = copy.deepcopy(w)
17print("w_copy.data:", w_copy.data)- Bằng cách dùng
copyreg, bạn có thể cung cấp các quy tắc tái tạo ảnh hưởng tới cảpicklevàdeepcopy. Đây là một API nâng cao, và trong hầu hết trường hợp__deepcopy__là đủ.
Những lưu ý và cạm bẫy thực tế
Có một số điểm quan trọng để đảm bảo hành vi đúng khi dùng mô-đun copy. Dưới đây chúng tôi giải thích các cạm bẫy thường gặp trong phát triển và cách tránh chúng.
- Hiệu năng
deepcopycó thể tiêu tốn đáng kể bộ nhớ và CPU, vì vậy hãy cân nhắc kỹ xem nó có thực sự cần thiết hay không. - Xử lý các phần tử bạn muốn chia sẻ
Nếu bạn muốn một số thuộc tính, chẳng hạn như bộ nhớ đệm lớn, vẫn được chia sẻ, hãy cố ý tránh sao chép tham chiếu của chúng trong
__deepcopy__. - Trạng thái nội bộ bất biến Nếu bạn giữ dữ liệu bất biến ở bên trong, việc sao chép có thể không cần thiết.
- Luồng và tài nguyên bên ngoài Các tài nguyên không thể sao chép, như socket và handle tệp, hoặc là vô nghĩa khi sao chép hoặc sẽ gây lỗi, nên bạn phải tránh sao chép chúng ngay từ khâu thiết kế.
Ví dụ thực tế: một mẫu để cập nhật từ điển một cách an toàn
Khi cập nhật một đối tượng cấu hình phức tạp, ví dụ này dùng deepcopy để bảo vệ các thiết lập gốc.
1# Example: safely update a nested configuration using deepcopy
2import copy
3
4default_config = {
5 "db": {"host": "localhost", "ports": [5432]},
6 "features": {"use_cache": True, "cache_sizes": [128, 256]},
7}
8
9# Create a working copy to modify without touching defaults
10working = copy.deepcopy(default_config)
11working["db"]["ports"].append(5433)
12working["features"]["cache_sizes"][0] = 512
13
14print("default_config:", default_config)
15print("working:", working)Sử dụng deepcopy, bạn có thể tạo các cấu hình phái sinh một cách an toàn mà không có nguy cơ làm hỏng các thiết lập mặc định. Điều này đặc biệt hữu ích cho các cấu trúc lồng nhau có thể thay đổi như cấu hình.
Thực tiễn tốt nhất
Để sử dụng mô-đun copy một cách an toàn và hiệu quả, điều quan trọng là ghi nhớ các hướng dẫn thực tiễn sau.
- Chọn giữa sao chép nông và sâu dựa trên việc có thay đổi xảy ra hay không và phạm vi của chúng.
- Triển khai
__copy__và__deepcopy__trong các lớp tùy chỉnh để đạt được hành vi như mong đợi. - Vì sao chép sâu tốn kém, hãy giảm nhu cầu sao chép thông qua thiết kế khi có thể. Cân nhắc tính bất biến và các phương thức clone tường minh, cùng những kỹ thuật khác.
- Khi xử lý tham chiếu vòng, hoặc tận dụng
deepcopyhoặc tự cung cấp một cơ chế giốngmemo. - Thiết kế sao cho các tài nguyên bên ngoài như file handle và thread không bị sao chép.
Kết luận
Mô-đun copy là công cụ tiêu chuẩn để nhân bản đối tượng trong Python. Bằng cách hiểu đúng sự khác biệt giữa sao chép nông và sao chép sâu và triển khai hành vi sao chép tùy chỉnh khi cần, bạn có thể thực hiện nhân bản một cách an toàn và có thể dự đoán. Bằng cách làm rõ ngay ở giai đoạn thiết kế việc sao chép có thực sự cần thiết và những gì nên được chia sẻ, bạn có thể tránh các lỗi và vấn đề hiệu năng không cần thiết.
Bạn có thể làm theo bài viết trên bằng cách sử dụng Visual Studio Code trên kênh YouTube của chúng tôi. Vui lòng ghé thăm kênh YouTube.