Skip to content
💻 🧠 Code 1001 > 📑 Cheat Sheets > 🐍 Python Cheat Sheets > Copying Objects in Python.

Copying Objects in Python.

🔎 Why Is Object Copying Important in the First Place?

In Python, variables are not “boxes” containing data but rather “labels” or references pointing to objects in memory. When you perform a simple assignment, like this:

list_a = [1, 2, 3]
list_b = list_a

You are not creating a new list. You are simply creating another reference (list_b) that points to the exact same list object.

👉 Any change to list_a will immediately be reflected in list_b (and vice versa) because they are the same object.

Analogy: Imagine you have a Google Doc. If you send a friend a link to it, they will be editing your original document. That’s an assignment. However, if you select “File” → “Make a copy,” a completely independent document is created. The copy module in Python exists to create these kinds of independent copies.

⚙️ Two Types of Copying

The copy module provides two key functions:

  • copy.copy() — creates a shallow copy.
  • copy.deepcopy() — creates a deep copy.

The main difference between them is how they handle nested objects (like lists within lists or dictionaries within dictionaries).


🟦 Shallow Copy (copy.copy())

copy.copy() creates a new top-level container object but, instead of copying the nested objects, it inserts references to them into the new container.

import copy

original_list = [1, 2, [3, 4]]
shallow_copy = copy.copy(original_list)

# Modify the nested list in the original
original_list[2][0] = 99

print(f"Original: {original_list}")    # Output: Original: [1, 2, [99, 4]]
print(f"Shallow Copy: {shallow_copy}") # Output: Shallow Copy: [1, 2, [99, 4]]

As you can see, modifying the nested list affected both the original and the copy because they share that nested list.

ASCII Diagram:

original_list → [ 1, 2, ───► list_inner ]
shallow_copy  → [ 1, 2, ───► list_inner ]
                            list_inner   → [ 99, 4 ]

🟩 Deep Copy (copy.deepcopy())

copy.deepcopy() solves the problem of shared references. This function recursively traverses the entire object and creates full, independent copies of everything it encounters, including all nested objects.

import copy

original_list = [1, 2, [3, 4]]
deep_copy = copy.deepcopy(original_list)

# Modify the nested list in the original
original_list[2][0] = 99

print(f"Original: {original_list}") # Output: Original: [1, 2, [99, 4]]
print(f"Deep Copy: {deep_copy}")     # Output: Deep Copy: [1, 2, [3, 4]]

Now, the deep_copy is completely independent.

ASCII Diagram:

original_list → [ 1, 2, ───► list_a ]
deep_copy     → [ 1, 2, ───► list_b ]
                            list_a   → [ 99, 4 ]
                            list_b   → [ 3, 4 ]

📑 Cheat Sheet: copy.copy() vs copy.deepcopy()

Object TypeExamplecopy.copy() (shallow)copy.deepcopy() (deep)
list[1, 2, [3, 4]]A new list, but the nested [3, 4] is sharedA completely independent copy; the nested list is also copied
dict{"a": 1, "b": {"c": 2}}A new dict, but the nested {"c": 2} is sharedAn independent dictionary and all nested dictionaries
set{1, 2, (3, 4)}A new set; nested mutable elements would be sharedA new set; all nested elements are recursively copied
tuple(1, 2, [3, 4])Returns the same tuple, but the nested list is sharedA new tuple with a copy of the nested list
str, int, float, bool"hi", 42, 3.14, TrueReturns the same object (they are immutable, no need to copy)Same as shallow copy
custom classNode(1, Node(2))A new top-level object; nested objects are sharedA completely independent copy of the object and all nested objects
object with __slots__A class with __slots__Only the top-level object is copiedAll nested objects are copied if they support copying
file / socket / streamopen("file.txt")Error (uncopyable object)Error (uncopyable object)

🧪 Live Examples for the Table

📌 list

import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f"copy: {b}")      # [1, 2, [99, 4]]
print(f"deepcopy: {c}")  # [1, 2, [3, 4]]

📌 dict

import copy
a = {"a": 1, "b": {"c": 2}}
b = copy.copy(a)
c = copy.deepcopy(a)
a["b"]["c"] = 42
print(f"copy: {b}")      # {'a': 1, 'b': {'c': 42}}
print(f"deepcopy: {c}")  # {'a': 1, 'b': {'c': 2}}

📌 set

import copy
a = {1, 2, (3, 4)} # Sets can only contain immutable types
b = copy.copy(a)
c = copy.deepcopy(a)
print(b == c) # True (but they are two independent objects in memory)

📌 tuple

import copy
a = (1, 2, [3, 4]) # The tuple is immutable, but its list element is not
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f"copy: {b}")      # (1, 2, [99, 4])
print(f"deepcopy: {c}")  # (1, 2, [3, 4])

📌 Custom class

import copy
class Node:
    def __init__(self, value, child=None):
        self.value = value
        self.child = child

a = Node(1, Node(2))
b = copy.copy(a)
c = copy.deepcopy(a)
a.child.value = 99
print(f"copy: {b.child.value}")      # 99
print(f"deepcopy: {c.child.value}")  # 2

📌 Customizing Behavior: __copy__ and __deepcopy__

You can control how your own objects are copied by defining special methods in their class.

import copy
class Custom:
    def __init__(self, x):
        self.x = x
    def __copy__(self):
        print("Called __copy__!")
        # Return a new instance, but with the same reference to internal data
        return Custom(self.x)
    def __deepcopy__(self, memo):
        # memo is a dictionary to track already copied objects (prevents recursion)
        print("Called __deepcopy__!")
        # Create a completely new copy, including nested data
        return Custom(copy.deepcopy(self.x, memo))

obj = Custom([1, 2])
c1 = copy.copy(obj)       # Output: Called __copy__!
c2 = copy.deepcopy(obj)   # Output: Called __deepcopy__!

⚡ Performance and Pitfalls

  • Speed: copy.copy() is significantly faster because it doesn’t recursively traverse the entire data structure. copy.deepcopy() can be very slow for large and deeply nested objects.
  • Uncopyable Objects: System resources like open files, sockets, or streams cannot be copied. Attempting to do so will raise an error.
  • Cyclic References: deepcopy is smart enough to handle cyclic references (where object A refers to B, and B refers back to A) without getting into an infinite loop.

When to Use What: The Main Rules

  1. Use a shallow copy (copy.copy()) if your object contains only immutable data or if you intentionally want the copy and the original to share nested objects. It’s fast and efficient.
  2. Use a deep copy (copy.deepcopy()) when you need a complete, 100% independent copy of the original, especially when working with complex data structures (lists, dictionaries, class instances).
  3. For immutable objects (numbers, strings, tuples without mutable elements), copying is generally pointless—Python optimizes their use anyway.
  4. Remember you can customize the process with __copy__ and __deepcopy__ for full control.

Leave a Reply

Your email address will not be published. Required fields are marked *