A hands-on localhost lab comparing html.escape with chained str.replace for safe HTML escaping.
4 min read
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:
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).
| Arm | Pattern |
|---|---|
html.escape(s, quote=True) | stdlib, escapes quotes |
html.escape(s, quote=False) | leaves quotes |
manual &→<→>→quotes | correct order |
| wrong-order replace | cautionary |
Script: lab-evidence/105-html-escape-vs-manual/results/run_lab.py.
| Arm | ops/s |
|---|---|
| html.escape quote | 3052559 |
| manual replace quote | 2358936 |
| html.escape noquote | 4682437 |
| manual noquote | 5150710 |
| Sample | html.escape quote | manual quote |
|---|---|---|
| mixed | 237188 | 242654 |
| plain | 3884159 | 4285261 |
| long_mixed (23040 chars) | 6203 | 6200 |
Throughput converges when most characters need escaping — allocator/copy work dominates.
& firstEntities start with &. Replacing & last turns < into &lt;. This lab’s wrong-order arm differed from html.escape (wrong_order_differs=True). Always & first, or just call html.escape.
Text nodes and attributes differ: quotes matter inside attr="...". html.escape(..., quote=True) maps " → " and ' → '. Skipping quote escaping is a common attribute-injection footgun even when < is escaped.
html.escape in templates/APIs.quote=True when emitting attribute values.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.
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.
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.
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.
quote=False inside double-quoted attributes.Evidence: summary.json, summary.txt.
One Linux box. ASCII-heavy samples. Not full HTML sanitizer / bleach. Not JS or URL encoding.
On light specials, html.escape ~3052559 ops/s vs manual ~2358936 — same league. Prefer html.escape so entity order and quote handling stay correct.
Lab evidence
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/.
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.
Lab1 Oct 20264 min read
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.
Lab1 Oct 20264 min read
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.
Lab1 Oct 20264 min read