Modul `copy` Python

Modul `copy` Python

Artikel ini menjelaskan modul copy Python.

Berfokus pada perbedaan antara salinan dangkal dan salinan mendalam, kami memberikan penjelasan yang jelas—mulai dari mekanisme dasar duplikasi objek hingga penerapannya pada kelas kustom—disertai contoh praktis.

YouTube Video

Modul copy Python

Modul copy milik Python adalah modul standar untuk menangani duplikasi (penyalinan) objek. Tujuannya adalah memahami perbedaan antara salinan dangkal dan salinan mendalam serta dapat mengendalikan perilaku penyalinan untuk objek kustom.

Dasar: Apa itu salinan dangkal?

Di sini kami menunjukkan perilaku salinan dangkal untuk objek yang dapat diubah (mutable) seperti list dan dictionary. Salinan dangkal hanya menduplikasi objek tingkat atas dan membagikan referensi di dalamnya.

 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)

Dalam kode ini, daftar tingkat teratas diduplikasi, tetapi subdaftar di dalamnya dibagikan melalui referensi, sehingga shallow[1].append(99) juga tercermin pada original. Ini adalah perilaku khas dari salinan dangkal.

Apa itu salinan mendalam?

Salinan mendalam menduplikasi objek dan semua referensi internalnya secara rekursif, menghasilkan objek yang independen. Gunakan ini ketika Anda ingin menduplikasi struktur bertingkat yang kompleks dengan aman.

 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)

Pada contoh ini, perubahan pada deep tidak memengaruhi original. deepcopy menyalin seluruh graf objek, menghasilkan replika yang independen.

Cara memutuskan mana yang akan digunakan

Pilihan salinan yang digunakan harus ditentukan oleh struktur objek dan tujuan Anda. Jika Anda tidak ingin perubahan pada elemen internal yang dapat diubah memengaruhi objek asli, deepcopy adalah pilihan yang tepat. Ini karena ia menduplikasi seluruh objek secara rekursif, menciptakan salinan yang sepenuhnya independen.

Sebaliknya, jika menduplikasi hanya objek tingkat atas sudah cukup dan Anda mengutamakan kecepatan serta efisiensi memori, copy (salinan dangkal) lebih sesuai.

  • Anda ingin menghindari perubahan pada objek asli saat Anda memodifikasi elemen internal yang dapat diubah → gunakan deepcopy.
  • Menduplikasi hanya tingkat atas sudah cukup dan Anda ingin memprioritaskan performa (kecepatan/memori) → gunakan copy (salinan dangkal).

Menangani objek bawaan dan objek immutabel

Objek immutabel (misalnya int, str, dan tuple yang isinya immutabel) biasanya tidak perlu disalin. copy.copy dapat mengembalikan objek yang sama bila sesuai.

 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)

Karena menyalin objek immutabel memberikan sedikit manfaat efisiensi, objek yang sama dapat digunakan kembali. Hal ini jarang menjadi sesuatu yang perlu Anda khawatirkan dalam desain aplikasi.

Kelas kustom: mendefinisikan __copy__ dan __deepcopy__

Jika penyalinan default tidak sesuai dengan yang Anda harapkan untuk kelas Anda, Anda dapat menyediakan logika penyalinan sendiri. Jika Anda mendefinisikan __copy__ dan __deepcopy__, maka copy.copy dan copy.deepcopy akan menggunakannya.

 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
  • Dengan mengimplementasikan __copy__ dan __deepcopy__, Anda dapat secara fleksibel mengendalikan perilaku penyalinan yang khusus untuk kelas. Sebagai contoh, Anda dapat menangani kasus di mana atribut tertentu harus merujuk (berbagi) objek yang sama, sementara atribut lain harus diduplikasi sebagai objek yang benar-benar baru.
  • Argumen memo yang diteruskan ke __deepcopy__ adalah sebuah kamus (dictionary) yang mencatat objek yang sudah diproses selama penyalinan rekursif dan mencegah loop tak berujung yang disebabkan oleh referensi melingkar.
 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)
  • Dalam kode ini, variabel shallow_copy dibuat melalui 'shallow copy', sehingga hanya tingkat teratas dari objek yang diduplikasi, dan objek yang direferensikan secara internal (dalam hal ini, daftar children) dibagikan dengan root_node asli. Akibatnya, ketika Anda menambahkan node baru ke shallow_copy, daftar children yang dibagikan diperbarui dan perubahan tersebut juga tercermin dalam isi root_node.
  • Di sisi lain, variabel deep_copy dibuat melalui 'deep copy', sehingga struktur internal diduplikasi secara rekursif, menghasilkan pohon yang sepenuhnya independen dari root_node. Oleh karena itu, meskipun Anda menambahkan node baru ke deep_copy, hal itu tidak memengaruhi root_node.

Referensi siklik (objek rekursif) dan pentingnya memo

Ketika graf objek yang kompleks mereferensikan dirinya sendiri (referensi siklik), deepcopy menggunakan dictionary memo untuk melacak objek yang sudah disalin dan mencegah loop tak berujung.

 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))
  • Secara internal, deepcopy menggunakan memo untuk merujuk objek yang sudah diduplikasi, sehingga memungkinkan penyalinan yang aman bahkan ketika terdapat siklus. Anda memerlukan mekanisme serupa saat melakukan rekursi secara manual.

copyreg dan penyalinan kustom bergaya serialisasi (lanjutan)

Jika Anda ingin mendaftarkan perilaku penyalinan khusus yang terkoordinasi dengan pustaka, Anda dapat menggunakan modul standar copyreg untuk mendaftarkan bagaimana objek dikonstruksi (dibangun kembali). Ini berguna untuk objek kompleks dan tipe ekstensi 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)
  • Dengan copyreg, Anda dapat menyediakan aturan rekonstruksi yang memengaruhi pickle dan deepcopy. Ini adalah API tingkat lanjut, dan dalam banyak kasus __deepcopy__ sudah mencukupi.

Peringatan dan jebakan praktis

Ada beberapa poin penting untuk memastikan perilaku yang benar saat menggunakan modul copy. Di bawah ini kami jelaskan jebakan umum yang mungkin Anda temui dalam pengembangan dan cara menghindarinya.

  • Performa deepcopy dapat mengonsumsi memori dan CPU yang signifikan, jadi pertimbangkan dengan cermat apakah itu diperlukan.
  • Menangani elemen yang ingin Anda bagikan Jika Anda ingin beberapa atribut, seperti cache besar, tetap dibagikan, secara sengaja hindari menyalin referensinya di dalam __deepcopy__.
  • Keadaan internal immutabel Jika Anda menyimpan data immutabel di dalam, penyalinan mungkin tidak diperlukan.
  • Thread dan sumber daya eksternal Sumber daya yang tidak dapat disalin, seperti soket dan handle berkas, tidak ada gunanya disalin atau akan menimbulkan error, sehingga Anda harus menghindari menyalinnya sejak tahap desain.

Contoh praktis: pola untuk memperbarui dictionary secara aman

Saat memperbarui objek konfigurasi yang kompleks, contoh ini menggunakan deepcopy untuk melindungi pengaturan asli.

 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)

Dengan deepcopy, Anda dapat dengan aman membuat konfigurasi turunan tanpa berisiko merusak pengaturan default. Ini sangat berguna untuk struktur bertingkat yang dapat diubah, seperti konfigurasi.

Praktik terbaik

Untuk menggunakan modul copy secara aman dan efektif, penting untuk mengingat pedoman praktis berikut.

  • Pilih antara salinan dangkal dan mendalam berdasarkan apakah akan ada perubahan dan sejauh mana cakupannya.
  • Implementasikan __copy__ dan __deepcopy__ di kelas kustom untuk mencapai perilaku yang diharapkan.
  • Karena salinan mendalam mahal, kurangi kebutuhan penyalinan melalui desain bila memungkinkan. Pertimbangkan immutabilitas dan metode klon eksplisit, di antara teknik lainnya.
  • Saat berhadapan dengan referensi siklik, manfaatkan deepcopy atau sediakan mekanisme mirip memo secara manual.
  • Rancang agar sumber daya eksternal seperti handle file dan thread tidak disalin.

Kesimpulan

Modul copy adalah alat standar untuk duplikasi objek di Python. Dengan memahami perbedaan antara salinan dangkal dan mendalam dengan benar serta mengimplementasikan perilaku penyalinan kustom saat diperlukan, Anda dapat melakukan duplikasi secara aman dan dapat diprediksi. Dengan memperjelas pada tahap desain apakah penyalinan benar-benar diperlukan dan apa yang harus dibagikan, Anda dapat menghindari bug dan masalah performa yang tidak perlu.

Anda dapat mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Silakan periksa juga saluran YouTube kami.

YouTube Video