The list.copy() method returns a new list containing a shallow clone of the original elements.
Shallow Copy vs. References: Variable assignment (b = a) merely creates an alias pointing to the same memory location via id(). In contrast, a.copy() allocates an independent list container. However, for nested lists or dictionaries, elements remain shared references—requiring copy.deepcopy() for total isolation. To purge items from a copy, see list.remove() or list.clear().
The copy() method accepts zero arguments and returns a new list:
# Formal syntax of list.copy()
new_list = original_list.copy()
| Parameter | Type | Default | Description & Operational Rule |
|---|---|---|---|
| None | Void | N/A | The copy() method takes zero arguments. |
| Return | list |
N/A | A new list instance containing shallow clones of the original element pointers. |
| Time Complexity | O(N) | N/A | Requires linear time to allocate memory and copy pointers to the new container. |
| Auxiliary Space | O(N) | N/A | Allocates a new contiguous dynamic array in RAM. |
Modifying top-level elements of a shallow copy leaves the original list intact:
# 1. Basic shallow copy using list.copy()
original = ["Alex", "Ronald", "John", "Sarah"]
copied = original.copy()
print("Original list: ", original)
print("Copied list: ", copied)
# Appending to copied list does not affect original
copied.append("Michael")
print("Original unchanged: ", original)
print("Copied modified: ", copied)Terminal Output:
Original list: ['Alex', 'Ronald', 'John', 'Sarah']
Copied list: ['Alex', 'Ronald', 'John', 'Sarah']
Original unchanged: ['Alex', 'Ronald', 'John', 'Sarah']
Copied modified: ['Alex', 'Ronald', 'John', 'Sarah', 'Michael']
The assignment operator (=) creates a reference alias, while copy() allocates a distinct memory object:
# 2. Assignment (=) vs. Shallow Copy (.copy())
nums = [10, 20, 30]
# Variable alias (same memory address)
alias = nums
# Shallow copy (distinct memory address)
clone = nums.copy()
print("nums is alias: ", nums is alias)
print("nums is clone: ", nums is clone)
print("nums == clone: ", nums == clone)Terminal Output:
nums is alias: True
nums is clone: False
nums == clone: True
If a list contains nested lists or dictionaries, shallow copies only duplicate outer pointers. Modifying child containers mutates both copies:
# 3. The Nested Mutable Mutation Trap: Shallow vs. Deep Copy
import copy
nested_orig = [1, [2, 3], 4]
shallow_cl = nested_orig.copy()
deep_cl = copy.deepcopy(nested_orig)
# Modifying inner child list affects shallow copy but NOT deep copy!
nested_orig[1][0] = 999
print("Original modified: ", nested_orig)
print("Shallow affected: ", shallow_cl)
print("Deep isolated: ", deep_cl)Terminal Output:
Original modified: [1, [999, 3], 4]
Shallow affected: [1, [999, 3], 4]
Deep isolated: [1, [2, 3], 4]
Python provides four distinct syntax choices to shallow-copy lists:
# 4. Four ways to create a shallow copy in Python
data = ["a", "b", "c"]
copy1 = data.copy() # 1. Method call (Python 3.3+, explicit)
copy2 = data[:] # 2. Slicing idiom
copy3 = list(data) # 3. Constructor
import copy
copy4 = copy.copy(data) # 4. Standard library module
print("All copies equal to original: ", copy1 == copy2 == copy3 == copy4 == data)
print("All copies have unique memory:", len({id(c) for c in [data, copy1, copy2, copy3, copy4]}) == 5)Terminal Output:
All copies equal to original: True
All copies have unique memory: True
| Copy Syntax | Python Version | Readability & Style | Primary Use Case |
|---|---|---|---|
list.copy() |
Python 3.3+ | Explicit and highly readable | Recommended for general Python 3 code. |
list[:] |
Python 1.0+ | Concise slicing idiom | Common in performance-critical code bases. |
list(iterable) |
Python 1.0+ | Generic type constructor | Converting sets, tuples, or generators into lists. |
copy.copy(list) |
Python 1.0+ | Generic library function | Generic functions handling arbitrary object types. |
Using shallow snapshots to implement transactional rollback buffers:
# 5. Production Recipe: Transactional State Snapshot & Rollback
class DataBuffer:
def __init__(self, initial_records: list):
self._data = initial_records.copy()
self._snapshot = None
def begin_transaction(self):
# Save shallow snapshot before speculative edits
self._snapshot = self._data.copy()
def rollback(self):
if self._snapshot is not None:
self._data = self._snapshot.copy()
self._snapshot = None
def commit(self):
self._snapshot = None
def add(self, item):
self._data.append(item)
@property
def current(self):
return self._data
buf = DataBuffer(["User1", "User2"])
buf.begin_transaction()
buf.add("UnverifiedUser")
print("Speculative state:", buf.current)
buf.rollback()
print("Rolled-back state:", buf.current)Terminal Output:
Speculative state: ['User1', 'User2', 'UnverifiedUser']
Rolled-back state: ['User1', 'User2']Isolating worker functions to guarantee input parameter immutability in data pipelines:
# 6. Production Recipe: Worker Isolation in Data Pipelines
def worker_process(input_records: list) -> list:
"""Protects caller's original list from unexpected worker mutation."""
local_batch = input_records.copy()
local_batch.append("PROCESSED_FLAG")
return local_batch
master_stream = ["Order_101", "Order_102"]
worker_result = worker_process(master_stream)
print("Master stream untouched: ", master_stream)
print("Worker processed output: ", worker_result)Terminal Output:
Master stream untouched: ['Order_101', 'Order_102']
Worker processed output: ['Order_101', 'Order_102', 'PROCESSED_FLAG']
Never assume copy() isolates nested dictionaries or custom mutable objects without copy.deepcopy():
# 7. Common traps: Assuming copy() recursively protects sub-structures
records = [{"id": 1, "status": "pending"}]
snapshot = records.copy()
# Updating dictionary value affects both because inner dict is shared!
snapshot[0]["status"] = "completed"
print("Original status: ", records[0]["status"])
print("Snapshot status: ", snapshot[0]["status"])Terminal Output:
Original status: completed
Snapshot status: completed
Practice shallow vs deep copying, memory identity checks, and rollback state management in our interactive companion Jupyter Notebook:
Explore interactive code cells, benchmark memory addresses with id(), test deepcopy isolation, and run self-validating assertions.
Run these unit checks directly in Python or your local environment to verify copy() mechanics:
# 8. Self-verifying unit test suite for list.copy()
import copy
# Test 1: Basic shallow copy distinct identity
base = [1, 2, 3]
c1 = base.copy()
assert c1 == base
assert c1 is not base
# Test 2: Top-level independence
c1.append(4)
assert len(base) == 3 and len(c1) == 4
# Test 3: Slicing vs copy method identity equivalence
c2 = base[:]
assert c2 == base and c2 is not base
# Test 4: Deep copy isolation
matrix = [[10, 20]]
shallow_m = matrix.copy()
deep_m = copy.deepcopy(matrix)
matrix[0].append(30)
assert len(shallow_m[0]) == 2
assert len(deep_m[0]) == 1
print("All list.copy() unit assertions successfully verified!")Terminal Output:
All list.copy() unit assertions successfully verified!
list.copy() is strictly a shallow copy. It duplicates the outer container but shares inner references to nested objects.
Both run in O(N) time and have virtually identical performance in modern CPython. list.copy() is preferred for readability.
Import the standard library copy module and call copy.deepcopy(my_list).
[].copy() returns a new, independent empty list: [].
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.