AI Skill Report Card
Querying Unicode Character Data
Quick Start15 / 15
Pythonimport unicodedata # Basic lookups unicodedata.name('€') # 'EURO SIGN' unicodedata.category('A') # 'Lu' (Letter, uppercase) unicodedata.decimal('9') # 9 unicodedata.numeric('½') # 0.5 # Normalization (most common real-world use) s1 = unicodedata.normalize('NFC', 'café') # composed form s2 = unicodedata.normalize('NFD', 'café') # decomposed form s1 == s2 # False — same visual string, different byte representation! # Safe comparison of visually-equivalent strings def unicode_equal(a, b): return unicodedata.normalize('NFC', a) == unicodedata.normalize('NFC', b)
Recommendation▾
Add an example showing east_asian_width usage in a practical context (e.g., terminal width calculation)
Workflow14 / 15
Progress:
- Identify the problem type: comparison, validation, stripping, or inspection
- Choose the right normalization form (NFC/NFD/NFKC/NFKD)
- Apply the appropriate function(s)
- Handle
ValueErrorfor unassigned/invalid codepoints - Test with edge cases (combining marks, surrogate pairs, CJK, emoji)
1. Diagnose what's actually needed
| Symptom | Likely fix |
|---|---|
| Two "identical-looking" strings compare unequal | Normalize both with NFC before comparing |
| Need to strip accents/diacritics | Decompose with NFD, then filter out combining marks |
| Need to compare full-width vs half-width, or ligatures | Use NFKC/NFKD (compatibility forms) |
| Validating that input is a digit/letter/etc. | Use category(), decimal(), numeric() |
| Debugging garbled text or mojibake | Use name() and category() to inspect codepoints one by one |
2. Pick the right normalization form
NFC— canonical composition. Default choice for storage/comparison/general use.NFD— canonical decomposition. Use before stripping accents or doing per-character analysis.NFKC/NFKD— compatibility forms. Use when visually/semantically equivalent but differently-encoded characters (full-width "A" vs "A", ligature "fi" vs "fi") should be treated as the same. Lossy — don't use for round-tripping.
3. Apply functions
Pythonunicodedata.lookup('LATIN SMALL LETTER A') # 'a' — name to char unicodedata.name(chr(0x1F600)) # char to name unicodedata.category(c) # 'Lu','Ll','Nd','Zs','Cc', etc. unicodedata.bidirectional(c) # 'L','R','AL','EN', etc. unicodedata.combining(c) # canonical combining class (0 = not combining) unicodedata.mirrored(c) # 1 if char is mirrored in bidi text unicodedata.east_asian_width(c) # 'W','Na','A','H','F','N' unicodedata.decomposition(c) # raw decomposition mapping string unicodedata.is_normalized('NFC', s) # fast check without allocating result
Recommendation▾
Include a brief note on grapheme clustering libraries (e.g., regex module or grapheme package) as a pointer for when unicodedata isn't sufficient
Examples19 / 20
Example 1: Strip accents/diacritics from text
Input: "café naïve"
Pythondef strip_accents(s): nfd = unicodedata.normalize('NFD', s) return ''.join(c for c in nfd if unicodedata.category(c) != 'Mn') strip_accents("café naïve")
Output: "cafe naive"
Example 2: Compare user input for equality regardless of composition
Input: "e\u0301" (e + combining acute accent) vs "\u00e9" (é precomposed)
Pythona, b = "e\u0301", "\u00e9" a == b # False unicodedata.normalize('NFC', a) == unicodedata.normalize('NFC', b) # True
Output: False then True
Example 3: Validate that a string is all decimal digits (including non-ASCII)
Input: "٣٤٥" (Arabic-Indic digits)
Pythondef all_decimal(s): try: return all(unicodedata.decimal(c) is not None for c in s) except ValueError: return False [unicodedata.decimal(c) for c in "٣٤٥"]
Output: [3, 4, 5]
Example 4: Inspect a mystery character for debugging
Input: '\u200b' (appeared unexpectedly in scraped text)
Pythonc = '\u200b' unicodedata.name(c), unicodedata.category(c)
Output: ('ZERO WIDTH SPACE', 'Cf') — reveals it's a formatting control char, explains invisible-but-present-length bugs.
Recommendation▾
Consider a troubleshooting table mapping ValueError scenarios to fixes for quicker diagnosis
Best Practices
- Normalize at input boundaries (when reading files, receiving API/form data) rather than scattering normalization calls throughout code.
- Use NFC for storage and comparison by default; it's the most compact and widely expected form.
- Use NFKC for search/matching where visual equivalence matters more than exact representation (e.g., full-width Japanese input).
- Category
Mn= "Mark, nonspacing" — the standard way to identify combining accent marks for stripping. - Always provide a default to
decimal()/digit()/numeric()(e.g.,unicodedata.decimal(c, None)) instead of catchingValueErroreverywhere. - Use
is_normalized()to skip unnecessary work when checking already-normalized strings at scale. - Remember
unicodedatareflects a specific Unicode version (checkunicodedata.unidata_version); behavior can shift slightly across Python versions as the database updates.
Common Pitfalls
- Comparing strings without normalizing first — visually identical strings from different sources (macOS filesystem NFD vs. web input NFC) will silently fail
==. - Using NFKC/NFKD for data you need to round-trip — these are lossy; a full-width "A" becomes ASCII "A" and you can't get it back.
- Assuming
len(s)equals visual character count — combining marks, ZWJ emoji sequences, and surrogate-adjacent codepoints break this assumption;unicodedataalone won't group grapheme clusters (that needs a grapheme-segmentation library). - Forgetting
category()returns a 2-letter code, not a human-readable label — e.g.'Zs'is "Separator, space", not obvious without a lookup table. - Not handling unassigned codepoints —
name()raisesValueErrorfor characters without an assigned name; always catch or provide a default.