Skip to content
Python Basics: Escaping and Unescaping Text with html

Python Basics: Escaping and Unescaping Text with html

October 13, 2026

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 1

Save 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.py

Output:

<p>Tea &amp; &lt;biscuits&gt;</p>
Escaped twice: Tea &amp;amp; &amp;lt;biscuits&amp;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.py

Output:

<span title="Mina&#x27;s &quot;daily&quot; list &amp; 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: &pound;5 &#38; &#x2615;"
print(unescape(encoded))

nested = "&amp;lt;b&amp;gt;"
decoded_once = unescape(nested)
print("One pass:", decoded_once)
print("Two passes:", unescape(decoded_once))

Run it:

python3 demo_html_decode.py

Output:

Price: £5 & ☕
One pass: &lt;b&gt;
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_dir

References

Acknowledgements

This post was created with assistance from AI.

Last updated on