Skip to Content
Course content

266: The unicodedata Module

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was building a user-registration system for a client with a heavy presence in Europe. We hit a bug that drove the team crazy: a user from France kept getting "Incorrect Password" errors, even though they were absolutely certain they were typing it correctly. I spent an hour staring at the database logs, and to my eyes, the input string and the stored string were identical. Both had an "é" in the middle. But when I ran a simple equality check in the Python shell, it returned False.

What was happening is a classic Unicode trap. One string used a single "composed" character for the "é", while the other used a "decomposed" version—essentially a standard "e" followed by a hidden "combining accent" character. They look exactly the same on your screen, but to the computer, they are different sequences of bytes. This is where the unicodedata module becomes your best friend.

Solving the 'Same-But-Different' String Problem

The most critical tool in the unicodedata toolkit is normalize(). Normalization ensures that characters are represented in a consistent way, regardless of how they were typed or encoded by the source system. There are four main forms, but you'll mostly deal with NFC and NFD.

NFC (Normalization Form C) composes characters into their shortest possible form. NFD (Normalization Form D) decomposes them into their base components. If you're comparing strings from different sources, you should normalize both to the same form before checking for equality.

import unicodedata

# These look identical, but they aren't
s1 = '\u00e9'         # 'é' as a single character
s2 = 'e\u0301'        # 'e' + combining acute accent

print(f"Equal? {s1 == s2}")  # False

# Normalize both to NFC (Composed)
n1 = unicodedata.normalize('NFC', s1)
n2 = unicodedata.normalize('NFC', s2)

print(f"Normalized Equal? {n1 == n2}")  # True

I usually default to NFC because it's more compact and generally more compatible with web standards. Whenever you're dealing with user-provided text that might contain accents or non-Latin scripts, make this a standard part of your preprocessing pipeline.

Peeking Inside the Unicode Database

Beyond normalization, unicodedata lets you query the official Unicode Character Database. This is incredibly useful when you're debugging a string and encounter a character that looks like a space but isn't, or a symbol you don't recognize. The name() function tells you exactly what a character is, and category() tells you its functional type.

import unicodedata

char = '©'
print(unicodedata.name(char))  # COPYRIGHT SIGN

# Let's look at a weird non-breaking space
weird_space = '\u00a0'
print(unicodedata.name(weird_space))  # NO-BREAK SPACE

# Categories are short codes. 'Ll' is Lowercase Letter, 'Nd' is Decimal Number, 'Zs' is Space Separator.
print(unicodedata.category(weird_space))  # Zs

You can use these categories to build smarter filters. For example, if you want to strip everything from a string that isn't a letter or a number, you can check if the category starts with 'L' (Letter) or 'N' (Number) rather than relying on a fragile regular expression or a hardcoded list of characters. It's a much more robust way to handle international text.




📋 Practical Task

Building a Base-ASCII Slug Generator

In this exercise, you will create a function that converts a "fancy" Unicode string into a clean, ASCII-only "slug" (the kind used in URLs). Your goal is to remove accents and diacritics while keeping the base character.

Requirements:

  • Write a function called simplify_text(text).
  • Use unicodedata.normalize('NFD', text) to decompose the characters.
  • Filter out any characters that have the Unicode category 'Mn' (Non-Spacing Mark), which is where the accents live.
  • Encode the resulting string to ascii and decode it back to utf-8 to ensure any remaining non-ASCII characters are stripped or handled.
  • Test your function with the string: "Héllo, how is the weather in São Paulo?". The expected output should be "Hello, how is the weather in Sao Paulo?".
Rating
0 0

There are no comments for now.

to be the first to leave a comment.