Python Copying Objects: Shallow, Deep, and Replace
Understand assignment versus copying, shallow and deep copies of nested objects, recursive references, and copy.replace() for supported record-like objects.
Intermediate
The Lists lesson showed that assignment creates another name and that making a top-level list copy does not duplicate nested items. This lesson builds on that foundation:
copy provides general shallow and deep copy operations, plus a way to create a modified record without changing the original.
A shallow copy creates a new outer object
Use
copy.copy(obj) when you need a new outer compound object. The objects referenced by its items are normally shared with the original. That can be exactly what you need, but it does not isolate nested mutable data.
Output
Ready to run.
Quick check
Which two identity results should this shallow-copy example produce?
import copy
source = [["red"], ["blue"]]
shallow = copy.copy(source)
print(shallow is source)
print(shallow[0] is source[0])
Nested changes remain shared after a shallow copy
A shallow copy separates the outer container, not the objects inside it. If an inner dictionary or list changes, both containers can show that change because they still refer to the same child object.
Output
Ready to run.
Quick check
After shallow = copy.copy(source), what happens when you append to a nested list?
import copy
source = {"profile": {"roles": ["reader"]}}
shallow = copy.copy(source)
shallow["profile"]["roles"].append("editor")
print(source["profile"]["roles"])
Deep copy recursively copies nested objects
Use
copy.deepcopy(obj) when the copied object graph should have independent nested containers. It recursively copies compound objects where possible, so a mutation to a nested list in the copy does not change the corresponding list in the source. Immutable values may still be reused safely.
Output
Ready to run.
Quick check
Which operation is the better fit when you need independent nested lists and dictionaries?
import copy
source = {"settings": {"labels": ["draft"]}}
independent = copy.deepcopy(source)
independent["settings"]["labels"].append("final")
print(source["settings"]["labels"])
Deep copy handles recursive references
A compound object can refer back to itself, directly or through other objects.
deepcopy() keeps a memo of objects it has already copied during the operation, preventing endless recursion and preserving the copied graph’s internal relationships. Treat that memo as an implementation detail; do not manage it yourself for ordinary copying.
Output
Ready to run.
Quick check
What does the self-reference check print for the deep-copied recursive list?
import copy
source = []
source.append(source)
independent = copy.deepcopy(source)
print(independent is source)
print(independent is independent[0])
Use copy.replace() to change selected fields
Since Python 3.13,
copy.replace(obj, **changes) creates a new object of a supported record-like type while replacing only the named fields. It supports dataclasses, named tuples, and classes that implement
__replace__(). It is not a deep copy: fields you do not replace can still refer to the same nested objects.
Output
Ready to run.
Quick check
What does copy.replace(report, title="Final") guarantee?
from copy import replace
from dataclasses import dataclass
@dataclass
class Report:
title: str
labels: list[str]
original = Report("Draft", ["core"])
revised = replace(original, title="Final")
print(original.title, revised.title)
print(original.labels is revised.labels)
Choose the least surprising copy operation
Start with assignment when a second name should refer to the same object. Use a shallow copy when only the outer container needs to be new, a deep copy when nested mutable state should be independent, and
copy.replace() when you want a new supported record with selected fields changed. Deep copying can copy more than intended or be unsupported for resources such as open files and sockets; consider the object’s meaning before copying it.
Key takeaways
- Assignment creates another binding; it does not copy the object.
- A shallow copy creates a new outer compound object but usually shares its children.
- A deep copy recursively copies nested compound objects where possible and uses a memo to handle repeated references and cycles.
- copy.replace() creates a new supported record-like object with selected fields changed; it is not recursive deep copying.
- Copying has limits. Choose based on the structure and intended sharing rather than copying everything by default.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.