The dict.copy() method creates and returns a shallow copy of a dictionary, allocating a brand-new outer dictionary object in memory.
The Shallow Copy Rule: Top-level keys and values are duplicated into the new container. However, if any values are mutable objects (such as nested lists, sets, or inner dictionaries), the copied dictionary stores references to those exact same inner objects rather than duplicating them recursively.
The standard CPython signature for dict.copy() is:
dictionary.copy()
| Component | Type | Parameters? | Behavior & Description |
|---|---|---|---|
| Arguments | None | None | copy() takes zero arguments. Passing parameters raises TypeError: copy() takes no arguments. |
| Return Value | dict |
— | Returns a newly allocated shallow copy of the dictionary with independent top-level key mappings. |
Python developers must distinguish between three distinct memory mechanisms when working with dictionaries:
original = {"name": "Alice", "score": 95}
# 1. Assignment (=): Both names reference the exact same object in memory
assigned = original
print("Assignment identity:", assigned is original) # True
# 2. Shallow Copy (dict.copy()): New dictionary object created
shallow = original.copy()
print("Shallow copy identity:", shallow is original) # False
# Mutating shallow copy top-level keys leaves original untouched
shallow["score"] = 100
print("Original score:", original["score"]) # 95 (preserved!)
print("Shallow score: ", shallow["score"]) # 100
While top-level keys in a shallow copy are independent, nested mutable objects are shared between the original and the copied dictionary:
user_profile = {
"username": "bob_dev",
"permissions": ["read", "write"] # Mutable inner list!
}
shallow_clone = user_profile.copy()
# Modifying the inner list through the copy
shallow_clone["permissions"].append("admin")
# The original's permissions list was also modified!
print("Original permissions:", user_profile["permissions"])
# Output: ['read', 'write', 'admin']
When dictionaries contain nested lists, sets, or sub-dictionaries that must remain completely independent, import the standard copy module and call deepcopy():
import copy
source_data = {
"user": "bob_dev",
"roles": ["dev", "tester"],
"meta": {"env": "staging"}
}
# Recursively duplicate all nested objects
deep_clone = copy.deepcopy(source_data)
deep_clone["roles"].append("admin")
deep_clone["meta"]["env"] = "production"
print("Source roles:", source_data["roles"]) # ['dev', 'tester'] (unmodified!)
print("Source env: ", source_data["meta"]["env"]) # 'staging' (unmodified!)
Understand the memory allocation, speed, and isolation characteristics across copying strategies:
| Mechanism | Outer Dict Identity | Top-Level Keys Isolated? | Nested Mutable Objects Isolated? | Performance & Overhead |
|---|---|---|---|---|
b = a |
Same (a is b) |
No (shared alias) | No (shared alias) | Fastest — O(1) pointer assignment. |
b = a.copy() |
New (a is not b) |
Yes | No (references shared) | Fast — Shallow copy of keys/pointers. |
b = copy.deepcopy(a) |
New (a is not b) |
Yes | Yes (recursively copied) | Slower — Full recursive traversal and object instantiation. |
Accept an optional configuration dictionary from a caller and apply overrides without mutating the caller's dictionary:
def initialize_connection(options: dict) -> dict:
# Create shallow copy to protect caller's dictionary
settings = options.copy()
settings.setdefault("timeout", 30)
settings.setdefault("retries", 3)
return settings
user_opts = {"host": "db.example.com"}
active_cfg = initialize_connection(user_opts)
print("Active config:", active_cfg)
print("User config: ", user_opts) # Unmodified!
Stage temporary record updates in a shallow clone and commit only when all validations pass:
master_record = {"id": 402, "status": "PENDING", "attempts": 0}
staged = master_record.copy()
staged["status"] = "VERIFIED"
staged["attempts"] += 1
# Commit changes after verification
master_record.update(staged)
print("Committed master record:", master_record)
A shallow copy only copies the outer hash table. Modifying nested lists, dictionaries, or custom objects inside a shallow copy will mutate the original structure. Use copy.deepcopy() when nested structures must be isolated.
Calling copy.deepcopy() on flat dictionaries with primitive values (strings, integers, floats) adds unnecessary performance and memory overhead. Use d.copy() for flat dictionaries.
Run this complete test harness verifying top-level isolation, shallow reference sharing, and deepcopy independence:
import copy
class CopyVerifier:
@staticmethod
def test_shallow_isolation(source: dict) -> bool:
clone = source.copy()
return clone is not source
# Verification unit assertions
v = CopyVerifier()
# Test 1: Top-level independence
d1 = {"a": 1, "b": 2}
c1 = d1.copy()
assert v.test_shallow_isolation(d1) is True
c1["a"] = 99
assert d1["a"] == 1 # Original untouched
# Test 2: Shallow copy shares nested list reference
d_nested = {"items": [10, 20]}
c_shallow = d_nested.copy()
c_shallow["items"].append(30)
assert d_nested["items"] == [10, 20, 30] # Shared reference
# Test 3: Deepcopy isolates nested list
c_deep = copy.deepcopy(d_nested)
c_deep["items"].append(40)
assert d_nested["items"] == [10, 20, 30] # Deep clone isolated
print("All dict.copy() unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all dictionary copy exercises interactively, test shallow copy vs deepcopy behavior, inspect nested mutation side effects, and verify assertions in Google Colab.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.