html.escape

Five replacements, & first: the three markup characters always, both quote characters unless quote=False. Non-ASCII text, newlines and everything else pass through untouched.

html functionPython 3.2+Live demo
Common call
html.escape(user_text)
Returns
'&lt;b&gt;Tom &amp; Jerry&lt;/b&gt;' for '<b>Tom & Jerry</b>'
Replaces
cgi.escape (removed in 3.8) and chains of .replace() calls
Watch out
escaping already-escaped text escapes every & again
html.escape(ss — The text to escape. Must be a str — bytes and numbers fail (convert with str() first).type: str · required, quotequote — Also replace " with &quot; and ' with &#x27;. Keep it True for attribute values; False is fine for element content.type: bool · default: True=True)
→ str

Demo

Live evaluation
The default: & < > " and ' are all replaced.
Try:
Inputs
textstrany text
Code
import html
html.escape('<a href="/x">Tom & Jerry</a>')
Result
'&lt;a href=&quot;/x&quot;&gt;Tom &amp; Jerry&lt;/a&gt;'

Every & is replaced first, then < and >, then the two quote characters — so the output of escape() can never contain a bare markup character, and the double quote in the breakout attempt can no longer end the value="..." attribute. The apostrophe becomes the hex reference &#x27;, not a named one. Already-escaped input gets its & escaped again: escape() cannot tell text from markup.

Parameters

NameTypeRequiredDescription
sstryesThe text to escape. Must be a str — bytes and numbers fail (convert with str() first).
quoteboolno (True)Also replace " with &quot; and ' with &#x27;. Keep it True for attribute values; False is fine for element content.

Return value

str — A new string with the special characters replaced; everything else unchanged.

Common patterns

Escape at output time
Store and process raw text; escape only where it is written into HTML.
import html
rows = "".join(f"<li>{html.escape(name)}</li>" for name in names)
Escape non-str values
escape() calls str methods, so convert numbers, None and objects first.
import html
cell = html.escape(str(value))
A tiny escaping helper for templates
Wrap escape() so every interpolated value goes through it.
import html
def render(template, **values):
    return template.format(**{k: html.escape(str(v)) for k, v in values.items()})

render("<p>{name}</p>", name="<b>x</b>")

Examples

1. The apostrophe becomes a hex reference
import html html.escape("It's")
Returns
'It&#x27;s'
2. quote=False keeps quotes
import html html.escape("It's", quote=False)
Returns
"It's"
3. Nothing to escape
import html html.escape('plain text 100%')
Returns
'plain text 100%'
4. Non-ASCII is left as it is
import html html.escape('naïve café 😀')
Returns
'naïve café 😀'
5. The output is longer
import html len('<'), len(html.escape('<'))
Returns
(1, 4)
6. unescape() reverses it exactly
import html s = '<a title="x">\'R&D\'</a>' html.unescape(html.escape(s)) == s
Returns
True
7. Numbers must be converted first
import html html.escape(3.5)
Returns
AttributeError: 'float' object has no attribute 'replace'
8. bytes are rejected
import html html.escape(b'<b>')
Returns
TypeError: a bytes-like object is required, not 'str'

Pitfalls

1. Double escaping
Escaping a value that was already escaped (in the database, by a template engine, by an earlier call) shows the reference text on the page.
escaped twice
import html
html.escape('&lt;b&gt;')
'&amp;lt;b&amp;gt;'
escape raw text once
import html
html.escape('<b>')
'&lt;b&gt;'
2. quote=False for attribute values
A " inside the value closes the attribute and injects new attributes.
quote=False
import html
v = html.escape('x" onclick="go()', quote=False)
f'<p title="{v}">'
'<p title="x" onclick="go()">'
quote=True (default)
import html
v = html.escape('x" onclick="go()')
f'<p title="{v}">'
'<p title="x&quot; onclick=&quot;go()">'
3. Passing bytes
escape() replaces with str arguments, so a bytes value fails with a confusing message. Decode first.
bytes
import html
html.escape(b'a < b')
TypeError: a bytes-like object is required, not 'str'
decode
import html
html.escape(b'a < b'.decode())
'a &lt; b'

When to use

Use it
  • Writing untrusted text into HTML element content or a quoted attribute
  • Showing code or markup literally on a web page
Reach for something else
  • Values inside <script> or <style> — escaping does not make them safe there
  • URLs in href/src → validate the scheme, and use urllib.parse.quote for query values
  • Whole pages → a template engine that auto-escapes (Jinja2, Django)

Notes

CPython impl
s.replace("&", "&amp;") first, then < → &lt;, > → &gt;, and with quote: " → &quot;, ' → &#x27; — pure Python, no regex
Order
The & replacement must run first, otherwise the & of the new references would be escaped again
Inverse
html.unescape(html.escape(s)) == s for every str s

FAQ

Five: & < > always, plus " and ' when quote is true (the default). The double quote becomes the named reference quot and the apostrophe the hex reference &#x27;. Nothing else changes — accented letters and emoji are left as they are, which is correct for a UTF-8 page.