El módulo unicodedata permite acceder a la base oficial de caracteres Unicode integrada en la versión de Python en ejecución. Una aplicación puede consultar nombres, categorías, valores numéricos, clases bidireccionales, marcas combinantes, descomposiciones y formas normalizadas. Estas funciones son esenciales en búsquedas, importación de datos, identificadores, slugs, comparaciones, validación e internacionalización.
Esta guía explica las APIs principales y muestra cómo normalizar sin destruir información. Python 3.14.6 documenta la Unicode Character Database 16.0.0.
Consultar la versión Unicode
import unicodedata
print(unicodedata.unidata_version)La versión puede cambiar nombres, categorías y caracteres reconocidos. Los sistemas que guardan claves normalizadas deben registrar la versión de Python y probar las actualizaciones.
Obtener el nombre de un carácter
name() devuelve el nombre oficial. Algunos caracteres no tienen nombre asignado, por lo que conviene proporcionar un valor por defecto.
print(unicodedata.name("½"))
print(unicodedata.name("\uFFFF", "SIN NOMBRE"))Los nombres sirven para diagnóstico e informes. Son identificadores estandarizados, no traducciones para el usuario.
Buscar por nombre
lookup() realiza la operación inversa y genera KeyError si el nombre no existe.
caracter = unicodedata.lookup("LEFT CURLY BRACKET")
print(caracter)También admite aliases y secuencias nombradas incluidas en la base. Valida nombres de usuarios y trata el error.
Categoría general
category() devuelve un código de dos letras. La primera indica un grupo amplio, como letras, marcas, números, puntuación, símbolos, separadores, controles o caracteres no asignados.
for caracter in ["A", "a", "9", "!", " "]:
print(caracter, unicodedata.category(caracter))Ejemplos: Lu para mayúscula, Ll para minúscula, Nd para dígito decimal y Zs para separador de espacio. Las categorías no son una política completa de seguridad.
decimal, digit y numeric
Unicode distingue varios conceptos numéricos. decimal() trata dígitos decimales, digit() incluye otras formas y numeric() cubre fracciones y numerales.
print(unicodedata.decimal("٩"))
print(unicodedata.digit("⁹"))
print(unicodedata.numeric("½"))Define si un campo acepta solo ASCII, dígitos decimales internacionales o cualquier carácter numérico Unicode.
Marcas combinantes
combining() devuelve la clase combinante canónica. Cero suele indicar que no existe una clase combinante definida.
texto = "a\u0301"
for c in texto:
print(repr(c), unicodedata.name(c), unicodedata.combining(c))La secuencia contiene una letra y un acento separado. Puede verse igual que á aunque tenga longitud y bytes distintos.
Por qué normalizar
Unicode permite representaciones canónicamente equivalentes. Sin normalización, comparaciones, claves de base, cachés e índices pueden tratar textos visualmente iguales como distintos.
a = "café"
b = "cafe\u0301"
print(a == b)
print(unicodedata.normalize("NFC", a) == unicodedata.normalize("NFC", b))Normalizar no resuelve diferencias de caja, puntuación, idioma ni caracteres visualmente confundibles.
NFC y NFD
NFD aplica descomposición canónica. NFC descompone y luego recompone cuando existe una forma precompuesta.
nfd = unicodedata.normalize("NFD", "acción")
nfc = unicodedata.normalize("NFC", nfd)NFC es una opción común para guardar y comparar texto humano. NFD resulta útil para analizar marcas o crear una clave auxiliar sin acentos.
NFKC y NFKD
Las formas de compatibilidad pueden sustituir variantes estilísticas o históricas por equivalentes simples.
print(unicodedata.normalize("NFKC", "Ⅳ"))
print(unicodedata.normalize("NFKC", "Full"))Estas transformaciones pueden perder distinciones. Úsalas en búsqueda o identificadores solo con una política definida. No las apliques ciegamente a contraseñas, firmas, documentos legales o contenido tipográfico.
Comprobar la forma
is_normalized() comprueba NFC, NFD, NFKC o NFKD.
texto = "café"
if not unicodedata.is_normalized("NFC", texto):
texto = unicodedata.normalize("NFC", texto)Normalizar directamente suele ser suficiente; la verificación ayuda en auditorías y métricas.
Quitar acentos con cuidado
Un patrón habitual descompone y elimina marcas sin espaciado. No es una transliteración universal.
def sin_acentos(texto):
descompuesto = unicodedata.normalize("NFD", texto)
filtrado = "".join(
c for c in descompuesto
if unicodedata.category(c) != "Mn"
)
return unicodedata.normalize("NFC", filtrado)Usa el resultado como clave auxiliar, no como reemplazo del original. Muchas letras no se convierten correctamente así.
Descomposición
decomposition() devuelve puntos de código hexadecimales y a veces una etiqueta de compatibilidad.
print(unicodedata.decomposition("Ã"))
print(unicodedata.decomposition("①"))Es útil para diagnóstico. Para transformar texto normalmente se debe usar normalize().
Texto bidireccional
bidirectional() devuelve la clase bidi y mirrored() identifica símbolos que pueden reflejarse en texto de derecha a izquierda.
print(unicodedata.bidirectional("٧"))
print(unicodedata.mirrored(">"))Estas propiedades no sustituyen un motor de layout. Controles bidireccionales pueden ocultar el orden visual en código, logs y archivos; las herramientas de seguridad deben ofrecer vistas escapadas.
Ancho asiático
east_asian_width() clasifica caracteres como estrechos, anchos, fullwidth, halfwidth, ambiguos o neutros.
for c in "A界F":
print(c, unicodedata.east_asian_width(c))La propiedad ayuda, pero no calcula por sí sola el ancho final de terminal. Marcas, emojis y configuración siguen influyendo.
Casefold y normalización
Para comparación sin caja, normaliza y usa casefold().
def clave_busqueda(texto):
return unicodedata.normalize("NFKC", texto).casefold()
print(clave_busqueda("Straße") == clave_busqueda("STRASSE"))Conserva siempre el original y no uses una clave simplificada como prueba de identidad.
Caracteres confundibles
La normalización no une todos los caracteres visualmente similares. Letras latinas, griegas y cirílicas pueden parecer iguales y seguir siendo distintas. Los homógrafos afectan usuarios, dominios, paquetes e identificadores.
Usa restricciones de scripts, detección especializada y revisión humana en contextos sensibles.
Preparar una clave de búsqueda
def preparar_busqueda(texto):
texto = unicodedata.normalize("NFKC", texto)
texto = texto.casefold()
return " ".join(texto.split())Versiona esta función como parte del esquema del índice, porque una actualización Unicode puede cambiar resultados.
Errores frecuentes
- Comparar texto sin una forma normalizada.
- Usar NFKC donde importa conservar distinciones.
- Reemplazar el original por una copia sin acentos.
- Tratar cualquier número Unicode como dígito ASCII.
- Esperar que normalizar evite homógrafos.
- Usar ancho asiático como cálculo visual completo.
- Ignorar la versión Unicode.
Buenas prácticas
- Conserva siempre la entrada original.
- Define normalización por campo y finalidad.
- Normaliza ambos lados de la comparación.
- Versiona claves e índices derivados.
- Prueba idiomas y scripts relevantes.
- Usa
casefold()para comparación caseless. - Aplica controles extra en identificadores sensibles.
Guías relacionadas
Continúa con textwrap en Python, locale en Python, fnmatch en Python, pydoc en Python y linecache en Python.
Consulta la documentación oficial de unicodedata y el Unicode HOWTO.
Conclusión
unicodedata permite tratar texto internacional con reglas explícitas y reproducibles. La normalización mejora búsquedas y comparaciones, pero no sustituye políticas de idioma, identidad, seguridad o presentación. Conserva el original y elige cada transformación según el dominio.







