copy — Shallow and Deep Copy Operations
📚 Official Documentation & Resources
Primary Official Sources (REQUIRED)
- Python Official Library Documentation: https://docs.python.org/3/library/copy.html
- Python Official Tutorial: https://docs.python.org/3/tutorial/index.html
- Module Source Code: https://github.com/python/cpython/blob/main/Lib/copy.py
Additional Authoritative Sources
- Real Python - Python's Assignment Operator: https://realpython.com/python-assignment-operator/
- GeeksforGeeks - Python Copy: https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/
- Python Module of the Week (PyMOTW) - copy: https://pymotw.com/3/copy/
- Stack Overflow copy questions: https://stackoverflow.com/questions/tagged/python+copy
- "Effective Python" by Brett Slatkin - Item 28: Prefer copying over mutable default arguments
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
| Function | Description | Return Type | Example |
|---|---|---|---|
copy(x) | Shallow copy of x | Same as x | copy.copy([1, 2, [3, 4]]) |
deepcopy(x, memo=None) | Deep copy of x | Same as x | copy.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
| Method | Description | Called By | Example |
|---|---|---|---|
__copy__(self) | Define shallow copy behavior | copy.copy() | Custom shallow copying |
__deepcopy__(self, memo) | Define deep copy behavior | copy.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
Related Modules
- 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