Connect with Amin Boulouma Official
Defensive Copying: Protecting Your Internal State
In Python, collections like list, dict, and set are mutable objects. When you store these in a class and expose them via a getter method, returning the original reference is a common source of “spooky action at a distance”, where external code accidentally modifies your internal state.
The Problem: The “Reference Leak”
If your class returns the original reference to its internal list, any external caller can mutate that list, causing your object’s internal state to change without its knowledge.
The Leaky Pattern:
class DocumentManager:
def __init__(self):
self._documents = ["doc1.txt", "doc2.txt"]
def get_documents(self):
# DANGER: Returning a reference to the internal list
return self._documents
# External code
manager = DocumentManager()
docs = manager.get_documents()
docs.append("malicious_doc.txt") # Modifies the internal state of 'manager'!
The Solution: Return a Copy
By returning self.documents.copy(), you return a shallow copy of the collection. The external caller now has its own version of the data, and any modifications it makes will not affect your class’s private state.
The Robust Pattern:
class DocumentManager:
def __init__(self):
self._documents = ["doc1.txt", "doc2.txt"]
def get_documents(self):
# SAFE: Returning a shallow copy
return self._documents.copy()
# External code
manager = DocumentManager()
docs = manager.get_documents()
docs.append("safe_doc.txt") # manager._documents remains unchanged!
When to Copy vs. When to Encapsulate
- Shallow vs. Deep Copies:
.copy()(or list slicing[:]) creates a shallow copy. If your list contains other mutable objects (like nested lists), those nested objects are still references! If you need to protect nested data, usecopy.deepcopy(). - Encapsulation: Alternatively, instead of returning the collection, expose methods that act on the collection (e.g.,
manager.add_document(),manager.get_document_count()). This is often the cleanest architectural choice. - Tuples: If the collection should never be modified, consider converting it to a
tuplebefore returning it. Tuples are immutable and provide a “read-only” guarantee that a list copy cannot.
Best Practices
- Default to Immutability: If a getter is meant to be read-only, ensure the caller cannot modify the original data.
- Performance Note: For very large collections, copying every time
get_documents()is called can have a performance cost. If performance is critical, return atupleor an iterator instead. - Be Intentional: Always ask yourself: Should the consumer be allowed to modify this state? If the answer is no, protect it with a copy or an immutable wrapper.
Defensive copying is a simple technique that prevents subtle, hard-to-track bugs. By keeping your class’s internal state encapsulated, you create a system that is predictable, robust, and much easier to debug.