Skip to main content

copy — Shallow and Deep Copy Operations

📚 Official Documentation & Resources

Primary Official Sources (REQUIRED)

Additional Authoritative Sources

IMPORTANT: Examples in this guide are adapted from the official Python documentation at https://docs.python.org/3/library/copy.html

Overview

The copy module provides generic (shallow and deep) copying operations. The distinction between shallow and deep copying is relevant to compound objects (objects that contain other objects, like lists or class instances). A shallow copy constructs a new compound object and inserts references to the objects found in the original. A deep copy constructs a new compound object and recursively inserts copies of the objects found in the original.

This module has been available since early Python versions and is essential for creating independent copies of mutable objects to avoid unintended side effects from shared references.

🎯 Key Characteristics

  • Two Copy Types: Shallow copies (references) vs deep copies (recursive copying)
  • Thread Safety: Copy operations are thread-safe for immutable objects
  • Memory Efficiency: Shallow copies share references, deep copies create full duplicates
  • Customizable: Objects can define __copy__() and __deepcopy__() methods
  • Handles Circular References: Deep copy can handle self-referencing objects
  • Type Preservation: Maintains original object types and class relationships

🔧 Prerequisites and Setup

Python Version Compatibility

  • Minimum Python version required: Python 1.4+ (module introduction)
  • Enhanced functionality: Python 2.5+ (improved circular reference handling)

Installation and Imports

# Standard library (no installation needed)
import copy
from copy import copy, deepcopy

📚 Basic Usage

Official Documentation Examples

Source: All examples adapted from https://docs.python.org/3/library/copy.html

Simple Example

import copy

# Shallow copy example
original_list = [[1, 2, 3], [4, 5, 6]]
shallow_copied = copy.copy(original_list)
deep_copied = copy.deepcopy(original_list)

# Modify the original
original_list[0][0] = 'X'

print(f"Original: {original_list}") # [['X', 2, 3], [4, 5, 6]]
print(f"Shallow copy: {shallow_copied}") # [['X', 2, 3], [4, 5, 6]] - affected!
print(f"Deep copy: {deep_copied}") # [[1, 2, 3], [4, 5, 6]] - unaffected!

Core Methods/Functions

import copy

# Basic copying operations (from official docs)
original = [1, [2, 3], 4]

# Shallow copy - copies object, but not nested objects
shallow = copy.copy(original)

# Deep copy - recursively copies all nested objects
deep = copy.deepcopy(original)

# Assignment just creates another reference
reference = original

# Demonstrate differences
original[1][0] = 'changed'
print(f"Original: {original}") # [1, ['changed', 3], 4]
print(f"Reference: {reference}") # [1, ['changed', 3], 4] - same object
print(f"Shallow: {shallow}") # [1, ['changed', 3], 4] - inner list shared
print(f"Deep: {deep}") # [1, [2, 3], 4] - completely independent

Common Patterns

# Pattern 1: Safe list manipulation (from official docs)
import copy

def process_data(data_list):
# Always work on a copy to avoid modifying original
working_copy = copy.deepcopy(data_list)
# ... perform operations on working_copy
return working_copy

# Pattern 2: Class instance copying (from official docs)
class Person:
def __init__(self, name, friends=None):
self.name = name
self.friends = friends or []

person1 = Person("Alice", ["Bob", "Charlie"])
person2 = copy.copy(person1) # Shallow copy
person3 = copy.deepcopy(person1) # Deep copy

person1.friends.append("David")
print(f"Person1 friends: {person1.friends}") # ['Bob', 'Charlie', 'David']
print(f"Person2 friends: {person2.friends}") # ['Bob', 'Charlie', 'David'] - shared!
print(f"Person3 friends: {person3.friends}") # ['Bob', 'Charlie'] - independent!

# Pattern 3: Dictionary copying with nested structures (from official docs)
original_dict = {
'numbers': [1, 2, 3],
'letters': ['a', 'b', 'c'],
'nested': {'key': 'value'}
}

shallow_dict = copy.copy(original_dict)
deep_dict = copy.deepcopy(original_dict)

original_dict['numbers'].append(4)
original_dict['nested']['key'] = 'modified'

print(f"Shallow dict nested: {shallow_dict['nested']}") # {'key': 'modified'}
print(f"Deep dict nested: {deep_dict['nested']}") # {'key': 'value'}

🔧 copy API Reference

Core Functions

FunctionDescriptionReturn TypeExample
copy(x)Shallow copy of xSame as xcopy.copy([1, 2, [3, 4]])
deepcopy(x, memo=None)Deep copy of xSame as xcopy.deepcopy([1, 2, [3, 4]])

Function Parameters

copy(x)

  • x: Object to copy
  • Returns: Shallow copy of x
  • Behavior: Creates new object with references to original's contents

deepcopy(x, memo=None)

  • x: Object to copy
  • memo: Dictionary used for memoization (internal use)
  • Returns: Deep copy of x
  • Behavior: Recursively creates copies of all nested objects

Custom Copy Behavior

Special Methods for Classes

MethodDescriptionCalled ByExample
__copy__(self)Define shallow copy behaviorcopy.copy()Custom shallow copying
__deepcopy__(self, memo)Define deep copy behaviorcopy.deepcopy()Custom deep copying

Detailed Method Examples

Basic Copying Operations

import copy

# Official docs example: Basic copying
a = [1, 2, 3]
b = [4, 5, 6]
c = [a, b]

# Assignment vs copying
d = c # Assignment - same object
e = copy.copy(c) # Shallow copy
f = copy.deepcopy(c) # Deep copy

# Test modifications
a.append(4)
print(f"Original c: {c}") # [[1, 2, 3, 4], [4, 5, 6]]
print(f"Assignment d: {d}") # [[1, 2, 3, 4], [4, 5, 6]] - same as c
print(f"Shallow e: {e}") # [[1, 2, 3, 4], [4, 5, 6]] - inner lists shared
print(f"Deep f: {f}") # [[1, 2, 3], [4, 5, 6]] - completely separate

Custom Copy Methods

import copy

# Official docs example: Custom copy behavior
class MyClass:
def __init__(self, value, data=None):
self.value = value
self.data = data or []
self._private = "secret"

def __copy__(self):
# Custom shallow copy
new_obj = MyClass(self.value)
new_obj.data = self.data # Share the same list
new_obj._private = self._private
return new_obj

def __deepcopy__(self, memo):
# Custom deep copy
new_obj = MyClass(copy.deepcopy(self.value, memo))
new_obj.data = copy.deepcopy(self.data, memo)
new_obj._private = self._private # Don't copy private data
return new_obj

# Usage
original = MyClass("test", [1, 2, 3])
shallow = copy.copy(original)
deep = copy.deepcopy(original)

original.data.append(4)
print(f"Original data: {original.data}") # [1, 2, 3, 4]
print(f"Shallow data: {shallow.data}") # [1, 2, 3, 4] - shared
print(f"Deep data: {deep.data}") # [1, 2, 3] - independent

Circular Reference Handling

import copy

# Official docs example: Handling circular references
class Node:
def __init__(self, value):
self.value = value
self.parent = None
self.children = []

def add_child(self, child):
child.parent = self
self.children.append(child)

# Create circular reference
root = Node("root")
child1 = Node("child1")
child2 = Node("child2")

root.add_child(child1)
root.add_child(child2)

# This creates a circular reference: root -> child1 -> parent (root)
# Deep copy handles this correctly
copied_root = copy.deepcopy(root)
print(f"Original root children: {len(root.children)}") # 2
print(f"Copied root children: {len(copied_root.children)}") # 2
print(f"Circular reference preserved: {copied_root.children[0].parent is copied_root}") # True

Dictionary and Set Copying

import copy

# Official docs example: Copying complex data structures
original_dict = {
'simple': 42,
'list': [1, 2, [3, 4]],
'dict': {'nested': {'deep': 'value'}},
'set': {1, 2, 3}
}

shallow_dict = copy.copy(original_dict)
deep_dict = copy.deepcopy(original_dict)

# Modify nested structures
original_dict['list'][2].append(5)
original_dict['dict']['nested']['deep'] = 'modified'

print("Original:", original_dict['dict']['nested']['deep']) # 'modified'
print("Shallow:", shallow_dict['dict']['nested']['deep']) # 'modified' - shared
print("Deep:", deep_dict['dict']['nested']['deep']) # 'value' - independent

Important Notes

When to Use Shallow vs Deep Copy

Use Shallow Copy When:

  • Objects contain only immutable items (strings, numbers, tuples)
  • You want to share references to large objects for memory efficiency
  • Working with simple collections of immutable objects

Use Deep Copy When:

  • Objects contain mutable nested objects (lists, dicts, custom objects)
  • You need complete independence between original and copy
  • Modifying the copy should never affect the original

Performance Considerations

import copy
import time

# Performance comparison
large_list = [[i] * 1000 for i in range(1000)]

# Timing shallow copy
start = time.time()
shallow = copy.copy(large_list)
shallow_time = time.time() - start

# Timing deep copy
start = time.time()
deep = copy.deepcopy(large_list)
deep_time = time.time() - start

print(f"Shallow copy time: {shallow_time:.4f}s")
print(f"Deep copy time: {deep_time:.4f}s")
# Deep copy is significantly slower for large nested structures

Common Gotchas

  • Mutable default arguments: Always use copy.deepcopy() for mutable defaults
  • Shallow copy surprise: Modifying nested objects affects the original
  • Memory usage: Deep copies can use significantly more memory
  • Object identity: Copied objects have different id() values

Thread Safety

import copy
import threading

# Copy operations are thread-safe for the operation itself
# But the objects being copied should be considered
shared_data = [1, 2, [3, 4]]

def worker():
local_copy = copy.deepcopy(shared_data)
# Safe to modify local_copy without affecting other threads
local_copy[2].append(5)

# Multiple threads can safely create copies
threads = [threading.Thread(target=worker) for _ in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()

Best Practices

# 1. Use copy for function parameters that modify inputs
def process_list(items):
items = copy.deepcopy(items) # Protect original
# ... modify items safely
return items

# 2. Copy when creating similar objects
base_config = {'timeout': 30, 'retries': 3, 'endpoints': []}
user_config = copy.deepcopy(base_config)
user_config['endpoints'].append('https://api.user.com')

# 3. Be explicit about copy intentions
# Good: clearly shows intention
user_data_backup = copy.deepcopy(user_data)

# Avoid: unclear whether sharing is intentional
user_data_backup = user_data
  • types: For working with type objects and type checking
  • pickle: For object serialization (another form of copying)
  • collections: For specialized container types that may need copying