Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
266: The unicodedata Module
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
asciiand decode it back toutf-8to 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?".
There are no comments for now.