ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 43

  1. Blog

copy.copy vs dict.copy Shallow: Localhost Lab

Aditya Challa·1 October 2026·4 min read

Hands-on
On this page
  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table (p50 ops/s)
  5. Shallowness check
  6. When copy.copy still wins
  7. Reading it for SRE work
  8. Method vs generic dispatch
  9. Spread and constructor peers
  10. Pitfalls
  11. Reproduce
  12. Limits
  13. Takeaway

Intro — what this post promises

Shallow-copy dicts and lists with dict.copy() / list[:] vs copy.copy. This lab reports ops/s on Linux localhost, plus a shared-inner correctness check.

Related links:

  • itertools batched vs chunk localhost lab
  • exitstack vs nested with localhost lab
  • chainmap vs dict merge localhost lab
  • ipaddress vs string prefix localhost lab
  • fractions vs float localhost lab
  • math fsum vs sum localhost lab
  • xml etree vs json localhost lab
  • logging formatter vs fstring localhost lab

Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. Differentiates from copy-vs-deepcopy (lab 50) — no deepcopy arm; focus is shallow method vs copy.copy dispatch.

Verdict up front (n=20000 copies of a 200-key dict / 200-elem list): dict.copy ~1890116 ops/s; copy.copy(dict) ~1571728; dict spread ~1804957; list[:] ~4222429; copy.copy(list) ~3346932.


Arms

ArmPattern
d.copy()dict method
copy.copy(d)generic shallow
dict unpack mergedict spread copy
dict(d)constructor copy
lst[:]list slice
copy.copy(lst)generic shallow list

Seven rounds, p50. Inner mutable shared across keys so we can prove shallowness.


Lab topology

n=20000 · dict_len=200 · list_len=200 · 7 rounds · p50
metric: ops/s = n / p50_s

Script: lab-evidence/126-copy-copy-vs-dict-copy/results/run_lab.py.


Lead table (p50 ops/s)

Armops/s
dict.copy1890116
copy.copy(dict)1571728
dict spread1804957
dict(d)1511895
list[:]4222429
copy.copy(list)3346932

Typed methods win: dict.copy edged copy.copy; list[:] led list arms.


Shallowness check

Both d.copy() and copy.copy(d) produced new outer objects (is not d) while sharing the nested dict (inner_shared=True for both). Mutating that inner still aliases — that is the shallow contract, not a bug.


When copy.copy still wins

Polymorphic helpers that accept dict or list or user types need copy.copy (or the copy protocol). Hot dict-only paths should call d.copy() and skip the generic dispatch. Lab 50 remains the place for deepcopy cost.


Reading it for SRE work

  • Config snapshot before mutate → d.copy() (fastest dict arm here).
  • List buffer fork → lst[:].
  • Generic util → copy.copy; accept the small tax (~1571728 vs ~1890116).
  • Nested mutables → either deepcopy (lab 50) or explicit nested copies — shallow will bite.

Method vs generic dispatch

On this box the gap is real but not dramatic for dicts: method ~1890116 ops/s vs copy.copy ~1571728. Spread landed close to dict.copy (~1804957) — fine for clarity, not a free lunch over .copy(). Lists show a wider slice-vs-copy.copy spread (~4222429 vs ~3346932).

If a profiler points at shallow copies in a hot loop, swap copy.copy(x) for the typed method before reaching for deepcopy or JSON round-trips.



Spread and constructor peers

dict(d) lagged at ~1511895 ops/s behind d.copy() ~1890116. Prefer the method in hot paths; keep constructor copies for readability in cold setup code. Remember: none of these arms isolate nested mutables — lab 50 still owns that comparison.


Pitfalls

  • Assuming shallow copy deep-clones nested lists/dicts.
  • Using copy.deepcopy “just in case” without measuring (see lab 50).
  • Comparing to dict merge that intentionally overrides keys.
  • Forgetting user classes need __copy__ for copy.copy.

Reproduce

python3 lab-evidence/126-copy-copy-vs-dict-copy/results/run_lab.py

Evidence: summary.json, summary.txt.


Limits

One Linux box. Builtin dict/list only. Not copy.deepcopy, not third-party containers.


Takeaway

Prefer dict.copy (~1890116 ops/s) and list[:] (~4222429) for typed shallow forks; use copy.copy when the type varies. Inner objects stay shared — measure deepcopy separately when you need isolation.

pythonperformanceshallow copydict.copycopy.copybenchmarklist sliceoptimization

Lab evidence

What I found running this

Lab 1 Oct 2026 IST. Python 3.13.5. n=20000: dict.copy 1890116 ops/s; copy.copy(dict) 1571728; list[:] 4222429. Shallow only (not lab 50). Affiliates: 0. Evidence: lab-evidence/126-copy-copy-vs-dict-copy/.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Related links

  • Plate 57

    memoryview vs bytes Slice: Localhost Lab

    1 Oct 2026

  • Plate 82

    shlex.split vs str.split: Localhost Lab

    1 Oct 2026

  • Plate 59

    enum.IntEnum vs int Status Codes: Localhost Lab

    1 Oct 2026

On this page

  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table (p50 ops/s)
  5. Shallowness check
  6. When copy.copy still wins
  7. Reading it for SRE work
  8. Method vs generic dispatch
  9. Spread and constructor peers
  10. Pitfalls
  11. Reproduce
  12. Limits
  13. Takeaway
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove