The set.copy() method creates and returns a shallow copy of an existing set, allocating a brand-new set container at a different memory address.
Container Isolation: Modifying the copied set (adding or removing elements) has zero effect on the original set. Because all elements inside a set must be hashable (and therefore immutable), a shallow copy provides practical full independence for all standard set operations.
The standard CPython signature for set.copy() is:
set_instance.copy()
| Component | Type | Parameters? | Behavior & Description |
|---|---|---|---|
| Arguments | None | None | copy() takes zero arguments. Passing parameters raises TypeError: copy() takes no arguments. |
| Return Value | set |
— | A newly allocated set container containing the same elements. The original set remains completely untouched. |
In Python, assigning one variable to another does not duplicate the underlying collection — it creates a shared alias. To work with an isolated set, you must explicitly create a copy:
original_tags = {"python", "fastapi", "docker"}
# 1. Variable Assignment (=): Shared alias
alias_tags = original_tags
alias_tags.add("kubernetes")
print("Original after alias add:", original_tags) # 'kubernetes' was added to original!
# 2. Shallow Copy (copy()): Independent new container
independent_tags = original_tags.copy()
independent_tags.add("terraform")
print("Original preserved: ", original_tags) # 'terraform' NOT in original!
print("Independent copy: ", independent_tags)
In dictionaries, shallow copying can lead to accidental mutations if inner values are mutable lists. However, Python sets strictly enforce that every element must be hashable:
Because Python sets disallow mutable objects (like lists or dictionaries) as members, set elements are generally immutable primitives (strings, numbers, frozensets, or tuples of primitives).
Consequently, modifying the membership of a copied set with add(), remove(), or discard() can never accidentally mutate the elements inside the original set.
Because frozenset instances are completely immutable, Python applies an internal optimization: calling copy() on a frozenset does not allocate new memory; it immediately returns the identical instance (fs.copy() is fs):
immutable_fs = frozenset([1, 2, 3])
fs_copy = immutable_fs.copy()
# For immutable frozensets, copy() returns the exact same object
print("Are frozenset copies identical objects?", fs_copy is immutable_fs) # True
# For standard mutable sets, copy() always allocates a new object
mutable_s = {1, 2, 3}
s_copy = mutable_s.copy()
print("Are mutable set copies identical objects?", s_copy is mutable_s) # False
Understand the performance and semantic differences between set duplication techniques:
| Mechanism | Syntax | Creates New Container? | Input Flexibility | Performance & Role |
|---|---|---|---|---|
set.copy() |
s2 = s.copy() |
Yes | Sets only | Fastest — Direct C-level set duplication. |
set() Constructor |
s2 = set(s) |
Yes | Any iterable (lists, tuples, ranges) | Fast — Standard idiom when converting other iterables into sets. |
| Variable Assignment | s2 = s |
No (shared alias) | Any object | Instantaneous — Does not duplicate; binds alias name. |
copy.deepcopy() |
copy.deepcopy(s) |
Yes | Any object | Slower — Unnecessary for sets unless elements are custom classes with mutable attributes. |
Save a snapshot of a working set before running destructive operations like difference_update():
active_sessions = {"sess_1", "sess_2", "sess_3"}
# Snapshot state for rollback capability
rollback_checkpoint = active_sessions.copy()
# Perform destructive purging of expired sessions
expired = {"sess_2", "sess_99"}
active_sessions.difference_update(expired)
print("Purged sessions: ", active_sessions) # {'sess_1', 'sess_3'}
print("Rollback checkpoint:", rollback_checkpoint) # {'sess_1', 'sess_2', 'sess_3'}
In recursive search and graph traversal algorithms, copy the visited set when branching into distinct search paths:
def explore_branch(current_node: str, visited: set) -> set:
# Fork visited set for this independent branch
branch_visited = visited.copy()
branch_visited.add(current_node)
return branch_visited
root_visited = {"NodeA"}
path1 = explore_branch("NodeB", root_visited)
path2 = explore_branch("NodeC", root_visited)
print("Path 1:", path1) # {'NodeA', 'NodeB'}
print("Path 2:", path2) # {'NodeA', 'NodeC'}
print("Root: ", root_visited) # {'NodeA'} (intact!)
Writing b = a does not create a backup set. Calling b.clear() or b.add() will directly mutate a. Always write b = a.copy().
Unlike the set() constructor which accepts an iterable, s.copy() accepts no parameters. Passing an argument raises TypeError: copy() takes no arguments.
Run this complete test harness verifying container independence, memory address separation, and mutation safety:
class SetCopyVerifier:
@staticmethod
def verify_isolation(source: set) -> bool:
clone = source.copy()
if clone is source:
return False
clone.add("__sentinel__")
return "__sentinel__" not in source
# Verification unit assertions
v = SetCopyVerifier()
# Test 1: Container isolation
original_set = {1, 2, 3}
assert v.verify_isolation(original_set) is True
# Test 2: Elements equality vs identity inequality
copied_set = original_set.copy()
assert copied_set == original_set
assert copied_set is not original_set
# Test 3: Element removal on copy does not affect original
copied_set.remove(1)
assert 1 in original_set
assert 1 not in copied_set
print("All set.copy() unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all set copy exercises interactively, test assignment vs copying, implement rollback snapshots, practice pathfinding branch isolation, 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.