Comprehensive Guide to Object Copying in Python: Shallow vs Deep Copy Mechanisms

Nov 20, 2025 · Programming · 9 views · 7.8

Keywords: Python object copying | shallow copy | deep copy | copy module | custom object copying

Abstract: This article provides an in-depth exploration of object copying mechanisms in Python, detailing the differences between shallow and deep copying along with their practical applications. Through comprehensive code examples, it systematically explains how to create independent object copies while avoiding unintended reference sharing. The content covers built-in data types, custom object copying strategies, and advanced usage of the copy module, offering developers a complete solution for object replication.

Fundamental Concepts of Object Copying in Python

In Python programming, object copying is a fundamental and crucial operation. Assignment operations (=) do not create new objects but establish reference bindings to existing objects. This means that when we need to create completely independent copies, specialized copying methods must be employed.

Distinction Between Shallow and Deep Copying

Python's copy module provides two primary copying approaches: shallow copy and deep copy. Shallow copy (copy.copy()) creates a new compound object and then inserts references to the objects found in the original into the new object. This copying approach results in shared internal objects for nested mutable objects.

import copy

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

# Modify internal list in shallow copy
shallow_copy[0].append(5)
print(f"Original object: {original_list}")  # Output: [[1, 2, 5], [3, 4]]
print(f"Shallow copy object: {shallow_copy}")  # Output: [[1, 2, 5], [3, 4]]

In contrast, deep copy (copy.deepcopy()) recursively copies all nested objects, creating completely independent copies:

# Deep copy example
deep_copy = copy.deepcopy(original_list)
deep_copy[0].append(6)
print(f"Original object: {original_list}")  # Output: [[1, 2, 5], [3, 4]]
print(f"Deep copy object: {deep_copy}")  # Output: [[1, 2, 5, 6], [3, 4]]

Copying Characteristics of Built-in Data Types

Different built-in data types exhibit distinct behaviors during copying. Lists provide a copy() method for shallow copying, and dictionaries have a similar copy() method. For immutable objects like tuples, strings, and frozensets, copying operations typically return the original object itself, as their immutability makes copying unnecessary.

# List copying
list_original = [1, 2, 3]
list_copy = list_original.copy()
list_copy.append(4)
print(f"Original list: {list_original}")  # Output: [1, 2, 3]
print(f"Copied list: {list_copy}")  # Output: [1, 2, 3, 4]

# Immutable object example
tuple_original = (1, 2, 3)
tuple_copy = tuple_original[:]  # Actually returns the same object
print(f"Tuples are same object: {tuple_original is tuple_copy}")  # Output: True

Custom Object Copying Implementation

For custom classes, copying behavior can be controlled by defining __copy__() and __deepcopy__() methods. These methods allow developers to precisely specify how objects should be copied.

class CustomObject:
    def __init__(self, value, nested_list):
        self.value = value
        self.nested_list = nested_list
    
    def __copy__(self):
        # Shallow copy implementation
        new_obj = CustomObject(self.value, self.nested_list)
        return new_obj
    
    def __deepcopy__(self, memo):
        # Deep copy implementation
        import copy
        id_self = id(self)
        if id_self in memo:
            return memo[id_self]
        
        new_obj = CustomObject(
            copy.deepcopy(self.value, memo),
            copy.deepcopy(self.nested_list, memo)
        )
        memo[id_self] = new_obj
        return new_obj

# Testing custom object copying
obj1 = CustomObject(10, [[1, 2], [3, 4]])
obj2 = copy.copy(obj1)
obj3 = copy.deepcopy(obj1)

obj2.nested_list[0].append(5)
obj3.nested_list[0].append(6)

print(f"Original object nested list: {obj1.nested_list}")  # Output: [[1, 2, 5], [3, 4]]
print(f"Shallow copy nested list: {obj2.nested_list}")  # Output: [[1, 2, 5], [3, 4]]
print(f"Deep copy nested list: {obj3.nested_list}")  # Output: [[1, 2, 5, 6], [3, 4]]

Memoization Mechanism in Deep Copying

copy.deepcopy() employs a memo dictionary to track already copied objects, a mechanism crucial for handling recursive data structures. The memo stores mappings from object IDs to their copies, preventing infinite recursion and duplicate copying issues.

# Deep copying of recursive data structures
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

# Create circular linked list
node1 = Node(1)
node2 = Node(2)
node1.next = node2
node2.next = node1  # Form circular reference

# Deep copy correctly handles circular references
node1_copy = copy.deepcopy(node1)
print(f"Original node ID: {id(node1)}, Copied node ID: {id(node1_copy)}")  # Different IDs
print(f"Original circular reference: {node1.next.next is node1}")  # Output: True
print(f"Copied circular reference: {node1_copy.next.next is node1_copy}")  # Output: True

Practical Applications and Best Practices

In practical development, the choice between shallow and deep copying depends on specific application requirements. Shallow copying is suitable for simple data structures with better performance, while deep copying is used for complex nested structures requiring completely independent copies. For objects containing large amounts of data, deep copying may incur significant memory and performance overhead, requiring careful consideration.

# Performance consideration example
import time

large_list = [list(range(1000)) for _ in range(100)]

# Shallow copy performance test
start_time = time.time()
shallow_copies = [copy.copy(large_list) for _ in range(10)]
shallow_time = time.time() - start_time

# Deep copy performance test
start_time = time.time()
deep_copies = [copy.deepcopy(large_list) for _ in range(10)]
deep_time = time.time() - start_time

print(f"Shallow copy time: {shallow_time:.4f} seconds")
print(f"Deep copy time: {deep_time:.4f} seconds")

By deeply understanding Python's object copying mechanisms, developers can better manage object lifecycles, avoid unintended side effects, and write more robust and maintainable code.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.