How do I properly clone a list in Python to prevent unexpected changes after assignment?
When using new_list = my_list, any modifications to new_list also affect my_list. Why does this happen, and what are the correct methods to create an independent copy of a list in Python?
For example:
>>> my_list = [1, 2, 3]
>>> new_list = my_list
>>> new_list.append(4)
>>> my_list
[1, 2, 3, 4]
What are the different ways to clone a list in Python, and when should each method be used?
When you use new_list = my_list in Python, you’re creating another reference to the same object in memory, not an independent copy. This means any modifications to the list through one variable will affect all other references to that same list object. To properly clone a list and create an independent copy, you should use shallow copy methods like list.copy(), slicing ([:]), or the copy module’s copy() function for simple cases, and copy.deepcopy() when dealing with nested mutable objects.
Contents
- Why Assignment Doesn’t Create a Copy
- Shallow Copy Methods
- Deep Copy for Nested Structures
- Performance and Use Case Comparison
- Practical Examples and Best Practices
Why Assignment Doesn’t Create a Copy
In Python, variables don’t store objects directly - they store references to objects in memory. When you use the assignment operator (=), you’re not creating a copy of the object; you’re simply creating another reference point to the same memory location.
Key Concept: The assignment operator, in Python, doesn’t copy. Ever. It adds a reference to an object, basically giving it a new name (as a variable), or putting that reference into a container (like a list or dictionary).
This behavior is fundamental to Python’s object model and applies to all objects, not just lists. When you modify the object through one variable, the change is visible through all other references to that same object.
# Both variables point to the same list object in memory
my_list = [1, 2, 3]
new_list = my_list # This creates another reference, not a copy
# Both variables reference the same object
print(id(my_list) == id(new_list)) # True - same memory address
# Modifying through one affects the other
new_list.append(4)
print(my_list) # [1, 2, 3, 4] - original list is modified
This reference behavior is efficient in terms of memory usage but can lead to unexpected bugs if you’re not aware of it.
Shallow Copy Methods
A shallow copy creates a new list object but the elements within the list are still references to the same objects. This is sufficient for lists containing immutable objects (like numbers, strings, tuples) but can cause issues with mutable nested objects.
Common Shallow Copy Techniques
-
list.copy()method (Python 3.3+)pythonoriginal = [1, 2, 3] copy = original.copy() -
Slicing
[:]pythonoriginal = [1, 2, 3] copy = original[:] -
list()constructorpythonoriginal = [1, 2, 3] copy = list(original) -
copy.copy()functionpythonimport copy original = [1, 2, 3] copy = copy.copy(original)
Shallow Copy Example
# Shallow copy works fine with immutable elements
original = [1, 2, 3, 4]
shallow_copy = original.copy()
shallow_copy.append(5)
print(original) # [1, 2, 3, 4] - unchanged
print(shallow_copy) # [1, 2, 3, 4, 5] - modified
However, with nested mutable objects, shallow copying creates shared references:
# Problem with nested mutable objects
original = [[1, 2], [3, 4]]
shallow_copy = original.copy()
# Modifying nested list affects both
shallow_copy[0].append(5)
print(original) # [[1, 2, 5], [3, 4]] - nested list modified!
print(shallow_copy) # [[1, 2, 5], [3, 4]]
As Mozilla Developer Network explains, shallow copying creates a new container but populates it with references to the same original objects.
Deep Copy for Nested Structures
When you need complete independence from the original list, especially with nested mutable objects, you should use deep copying. A deep copy creates a new object and recursively copies all objects within it, ensuring no shared references.
copy.deepcopy() Function
import copy
original = [[1, 2], [3, 4]]
deep_copy = copy.deepcopy(original)
# Modifying nested list doesn't affect original
deep_copy[0].append(5)
print(original) # [[1, 2], [3, 4]] - unchanged
print(deep_copy) # [[1, 2, 5], [3, 4]] - modified independently
When Deep Copy is Necessary
Use deep copy when:
- Your list contains nested mutable objects (lists, dictionaries, sets, custom objects)
- You need complete isolation between the original and copied list
- You’re working with complex data structures that might be modified independently
Deep Copy Definition: A deep copy creates a new compound object before inserting copies of the items found in the original into it in a recursive manner.
Performance and Use Case Comparison
Performance Characteristics
| Method | Time Complexity | Memory Usage | Best Use Case |
|---|---|---|---|
Assignment (=) |
O(1) | Minimal | When you intentionally want shared references |
| Shallow copy | O(n) | Moderate | Lists with immutable elements or shared nested objects |
| Deep copy | O(n) | High | Lists with nested mutable objects requiring isolation |
Method Selection Guide
# Assignment - intentional sharing
shared_data = process_data()
working_copy = shared_data # Both variables work on same data
# Shallow copy - when elements are immutable or can be shared
numbers_list = [1, 2, 3, 4]
backup = numbers_list.copy() # Safe for immutable elements
# Deep copy - when nested objects need isolation
complex_data = [[1, 2], {"key": "value"}, set([3, 4])]
independent_copy = copy.deepcopy(complex_data)
According to the official Python documentation, deep copy is related to nested structures: “If you have list of lists, then deepcopy copies the nested lists also, so it is a recursive copy.”
Practical Examples and Best Practices
Example 1: Simple List Copying
# For flat lists with immutable elements, shallow copy is sufficient
scores = [85, 92, 78, 96]
backup_scores = scores.copy() # or scores[:]
# Modify backup doesn't affect original
backup_scores.append(100)
print(scores) # [85, 92, 78, 96]
print(backup_scores) # [85, 92, 78, 96, 100]
Example 2: Nested List Handling
# For nested mutable objects, use deep copy
student_records = [
{"name": "Alice", "grades": [90, 85, 92]},
{"name": "Bob", "grades": [78, 82, 88]}
]
# Shallow copy would share nested dictionaries and lists
records_copy = copy.deepcopy(student_records)
# Modify copy independently
records_copy[0]["grades"].append(95)
print(records_copy[0]["grades"]) # [90, 85, 92, 95]
print(student_records[0]["grades"]) # [90, 85, 92] - unchanged
Example 3: Function Parameters
def process_data(data):
# Create a copy to avoid modifying the original
working_data = copy.deepcopy(data)
# Process the data
working_data.append("processed")
return working_data
original_data = [1, 2, 3]
result = process_data(original_data)
print(original_data) # [1, 2, 3] - unchanged
print(result) # [1, 2, 3, "processed"]
Best Practices
-
Always be explicit about your copying intention
- Use
=when you want shared references - Use
.copy()or[:]for shallow copies - Use
copy.deepcopy()for complete isolation
- Use
-
Consider the data structure complexity
- Flat lists with immutable elements: shallow copy
- Nested mutable objects: deep copy
- Large datasets: performance implications matter
-
Use appropriate method for your Python version
.copy()method available since Python 3.3- Slicing
[:]works in all versions copy.copy()andcopy.deepcopy()work in all versions
As Real Python explains, “Shallow copying creates a new object but references the same nested objects, leading to shared changes. Deep copying recursively duplicates all objects, ensuring full independence from the original.”
Sources
- Official Python Documentation - copy module
- GeeksforGeeks - Deep and Shallow Copy in Python
- GeeksforGeeks - Cloning or Copying a List
- Real Python - How to Copy Objects in Python
- Stack Overflow - Assignment vs Shallow Copy vs Deep Copy
- Medium - Assignment vs Shallow Copy vs Deep Copy in Python
- Python Engineer - Shallow vs Deep Copying
- Reddit - Shallow and Deep Copy Discussion
Conclusion
Understanding Python’s object reference model is crucial for avoiding unexpected behavior when working with lists. Assignment operations create references to the same object in memory, while shallow and deep copies create new objects with different levels of independence.
Key takeaways:
- Use assignment (
=) when you intentionally want multiple variables to reference the same object - Use shallow copy methods (
.copy(),[:],list(),copy.copy()) for lists with immutable elements or when nested objects can be safely shared - Use deep copy (
copy.deepcopy()) when you need complete isolation, especially with nested mutable objects like lists, dictionaries, or custom objects
Practical recommendations:
- For simple flat lists with immutable elements, shallow copying is efficient and sufficient
- For nested data structures, always consider whether you need deep copy to prevent unintended side effects
- Be explicit about your copying intention to make your code more maintainable and less error-prone
By choosing the appropriate copying method based on your data structure and requirements, you can prevent unexpected changes and write more reliable Python code.