AI Skill Report Card

Querying Unicode Character Data

A90·Aug 14, 2026·Source: Web
15 / 15
Python
import 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)
14 / 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 ValueError for unassigned/invalid codepoints
  • Test with edge cases (combining marks, surrogate pairs, CJK, emoji)

1. Diagnose what's actually needed

SymptomLikely fix
Two "identical-looking" strings compare unequalNormalize both with NFC before comparing
Need to strip accents/diacriticsDecompose with NFD, then filter out combining marks
Need to compare full-width vs half-width, or ligaturesUse NFKC/NFKD (compatibility forms)
Validating that input is a digit/letter/etc.Use category(), decimal(), numeric()
Debugging garbled text or mojibakeUse 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

Python
unicodedata.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
19 / 20

Example 1: Strip accents/diacritics from text Input: "café naïve"

Python
def 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)

Python
a, 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)

Python
def 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)

Python
c = '\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
  • 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 catching ValueError everywhere.
  • Use is_normalized() to skip unnecessary work when checking already-normalized strings at scale.
  • Remember unicodedata reflects a specific Unicode version (check unicodedata.unidata_version); behavior can shift slightly across Python versions as the database updates.
  • 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; unicodedata alone 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 codepointsname() raises ValueError for characters without an assigned name; always catch or provide a default.
0
Grade AAI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
15/15
Workflow
14/15
Examples
19/20
Completeness
19/20
Format
14/15
Conciseness
14/15