Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

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.

Levels: Intermediate ~18 min read

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.

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.

Interactive Runnable example
Edit

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.
Was this page helpful?

Mark this learning resource complete to track your learning progress.