Python Basics: Escaping and Unescaping Text with html
When a page should display Tea & <biscuits> literally, the text needs escaping. Python’s html module can escape text for HTML and decode HTML character references.
Before you start
These examples were tested on Linux with Python 3.12.14 and 3.13.5, using Bash and only the standard library. Make sure python3 runs one of those Python versions. The three scripts are independent and only print results; they do not open a browser or use the network.
Create a fresh directory for the example scripts. Keep this Bash session open for cleanup:
start_dir="$PWD"
demo_dir="$(mktemp -d)" || exit 1
cd "$demo_dir" || exit 1Save the following UTF-8 source files in this directory, then run each command there. The setup creates the directory; saving the scripts creates the only files.
Display literal text inside an element
Save as demo_html_text.py:
from html import escape
label = "Tea & <biscuits>"
escaped = escape(label, quote=False)
print(f"<p>{escaped}</p>")
print("Escaped twice:", escape(escaped, quote=False))Run it:
python3 demo_html_text.pyOutput:
<p>Tea & <biscuits></p>
Escaped twice: Tea &amp; &lt;biscuits&gt;escape() replaces &, <, and >. Here, quote=False leaves quotation marks alone because the value goes into an ordinary paragraph’s text. Escape the value, keeping the surrounding markup separate. The second line demonstrates double escaping: applying the function again also escapes the new ampersands.
Fill a quoted text attribute
Save as demo_html_attribute.py:
from html import escape
tooltip = '''Mina's "daily" list & notes'''
attribute = escape(tooltip)
print(f'<span title="{attribute}">Details</span>')Run it:
python3 demo_html_attribute.pyOutput:
<span title="Mina's "daily" list & notes">Details</span>The default quote=True escapes both kinds of quotation marks. Keep attribute values quoted, as shown.
Escaping depends on context. This pattern is for a normal text attribute such as title, not JavaScript, CSS, event handlers, or URL validation. html.escape() is not an HTML sanitizer: it does not selectively preserve safe tags or check link destinations. In a web application, prefer the framework’s context-appropriate automatic escaping rather than assembling arbitrary HTML by hand.
Decode character references
Save as demo_html_decode.py:
from html import unescape
encoded = "Price: £5 & ☕"
print(unescape(encoded))
nested = "&lt;b&gt;"
decoded_once = unescape(nested)
print("One pass:", decoded_once)
print("Two passes:", unescape(decoded_once))Run it:
python3 demo_html_decode.pyOutput:
Price: £5 & ☕
One pass: <b>
Two passes: <b>unescape() decodes named, decimal, and hexadecimal references using HTML5 rules. One call does not recursively decode the newly produced references. Decoding is not sanitizing or tag removal; do not insert decoded untrusted text into HTML without the appropriate escaping.
Clean up
After running all three examples, remove only their source files and the now-empty temporary directory:
rm -- demo_html_text.py demo_html_attribute.py demo_html_decode.py
cd "$start_dir" || exit 1
rmdir -- "$demo_dir"
unset start_dir demo_dirReferences
Acknowledgements
This post was created with assistance from AI.