ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 71

  1. Blog
  2. /Observability & SRE

html.escape vs Manual Replace: Localhost Lab

A hands-on localhost lab comparing html.escape with chained str.replace for safe HTML escaping.

Aditya Challa·30 September 2026·4 min read

Lab
On this page
  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table — light (p50 ops/s)
  5. Mixed / long check
  6. Correctness: replace `&` first
  7. Attribute vs text context
  8. Reading it
  9. Why stdlib anyway
  10. Template engines
  11. Quote flag checklist
  12. Not a sanitizer
  13. Pitfalls
  14. Reproduce
  15. Limits
  16. Takeaway

Intro — what this post promises

Escape strings for HTML: html.escape vs chained str.replace for & < > " '. This lab reports ops/s on Linux localhost.

Frame: prefer stdlib html.escape for correctness (entity order, quote flag). Speed is usually in the same band as a careful manual chain. Affiliates: 0.

Related links:

  • configparser vs json localhost lab
  • zlib vs gzip compress localhost lab
  • difflib vs set ops localhost lab
  • groupby vs manual localhost lab
  • methodcaller vs getattr localhost lab
  • queue vs deque handoff localhost lab
  • stringio vs list join localhost lab
  • hmac compare digest localhost lab

Lab honesty (1 Oct 2026 IST): Python 3.13.5. No Docker. Batched inner loops; p50 of 7 rounds.

Verdict up front (light string with specials): html.escape(quote=True) ~3052559 ops/s; manual replace ~2358936. On mixed, both land ~237188 vs ~242654 ops/s. Wrong replace order differs from stdlib (correctness footgun).


Arms

ArmPattern
html.escape(s, quote=True)stdlib, escapes quotes
html.escape(s, quote=False)leaves quotes
manual &→<→>→quotescorrect order
wrong-order replacecautionary

Lab topology

samples: plain · light · heavy · mixed · long_mixed
inner batch 500 (50 for long) · 7 rounds · p50
metric: ops/s = inner / p50_s

Script: lab-evidence/105-html-escape-vs-manual/results/run_lab.py.


Lead table — light (p50 ops/s)

Armops/s
html.escape quote3052559
manual replace quote2358936
html.escape noquote4682437
manual noquote5150710

Mixed / long check

Samplehtml.escape quotemanual quote
mixed237188242654
plain38841594285261
long_mixed (23040 chars)62036200

Throughput converges when most characters need escaping — allocator/copy work dominates.


Correctness: replace & first

Entities start with &. Replacing & last turns &lt; into &amp;lt;. This lab’s wrong-order arm differed from html.escape (wrong_order_differs=True). Always & first, or just call html.escape.


Attribute vs text context

Text nodes and attributes differ: quotes matter inside attr="...". html.escape(..., quote=True) maps " → &quot; and ' → &#x27;. Skipping quote escaping is a common attribute-injection footgun even when < is escaped.


Reading it

  • Default to html.escape in templates/APIs.
  • Manual chains are fine only if tested against stdlib output.
  • Set quote=True when emitting attribute values.
  • XSS needs more than escaping (CSP, context-aware encoding) — this lab is micro-escape only.

Why stdlib anyway

Even when manual replace matches today’s CPython output, future quote rules or additional characters are easy to miss in a DIY helper copied across services. Centralizing on html.escape keeps one well-tested path. Microbenchmarks here show you are not leaving large performance on the table by doing so.


Template engines

Many frameworks escape by default in HTML context. Bypassing that with “trusted” manual strings is how XSS slips in. If you must pre-escape, still prefer html.escape and mark the result safe only in the framework’s supported way — not by inventing another replace chain.


Quote flag checklist

Use quote=True (the default in recent Python) whenever the string may land in an HTML attribute. For element text, quote=False skips quote entities and runs a bit faster here (light noquote ~4682437 ops/s vs quote ~3052559), but only choose that when the surrounding context cannot interpret quotes as delimiters.


Not a sanitizer

Escaping special characters is necessary and not sufficient for safe HTML composition. It does not parse DOM, strip event handlers, or neutralize dangerous URLs. Pair escaping with a real policy (CSP, sanitizer library, framework autoescape) rather than treating this microbench as a security design.


Pitfalls

  • DIY replace with wrong entity order.
  • Escaping twice (double entities) in stacked middleware.
  • Using quote=False inside double-quoted attributes.
  • Trading stdlib for a micro-faster chain you will not maintain.

Reproduce

python3 lab-evidence/105-html-escape-vs-manual/results/run_lab.py

Evidence: summary.json, summary.txt.


Limits

One Linux box. ASCII-heavy samples. Not full HTML sanitizer / bleach. Not JS or URL encoding.


Takeaway

On light specials, html.escape ~3052559 ops/s vs manual ~2358936 — same league. Prefer html.escape so entity order and quote handling stay correct.

html.escapehtml escapestr.replacexss escapepython htmllocalhost labsreops/s

Lab evidence

What I found running this

Lab 1 Oct 2026 IST. Python 3.13.5. light quote=True: html.escape 3052559 ops/s; manual 2358936. mixed ~237188 vs 242654. wrong-order differs. Affiliates: 0. Evidence: lab-evidence/105-html-escape-vs-manual/.

Notes when a lab post goes up

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

Related links

  • Plate 12

    islice vs list Slice Windows: Localhost Lab

    Hands-on itertools.islice vs list slice window lab: real ops/s taking ranges from sequences, measured on Linux localhost in this hands-on lab for SREs.

    Observability & SRE · 1 Oct 2026

  • Plate 17

    difflib vs set Ops Similarity: Localhost Lab

    Hands-on difflib.SequenceMatcher vs set Jaccard token similarity: real ops/s on token lists, measured on Linux localhost in this hands-on lab for SREs.

    Observability & SRE · 30 Sept 2026

  • Plate 63

    Decimal vs float Sum: Localhost Lab

    Hands-on Decimal vs float cumulative sum lab: real ops/s plus a simple accuracy note (not financial advice), measured on Linux localhost (lab) for SREs.

    Observability & SRE · 30 Sept 2026

On this page

  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table — light (p50 ops/s)
  5. Mixed / long check
  6. Correctness: replace `&` first
  7. Attribute vs text context
  8. Reading it
  9. Why stdlib anyway
  10. Template engines
  11. Quote flag checklist
  12. Not a sanitizer
  13. Pitfalls
  14. Reproduce
  15. Limits
  16. Takeaway
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove