Errores de codificación y Unicode
Los errores de texto aparecen cuando los bytes se decodifican con el códec incorrecto, str y bytes se mezclan sin un límite explícito, o las cadenas Unicode visualmente idénticas comparan como desiguales debido a la falta de normalización.
Receta
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
# Siempre etiqueta los bytes; decodifica en el límite
raw: bytes = b"\xc3\xa9" # UTF-8 para é
text: str = raw.decode("utf-8")
# Escribiendo texto
path.write_text(text, encoding="utf-8")
path.write_bytes(raw)Cuándo usar esto:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff- La concatenación de
str+bytesgeneraTypeError - La búsqueda del usuario no encuentra coincidencias para caracteres acentuados
- Los registros muestran
mojibakecomoéen lugar deé
Ejemplo funcional
import unicodedata
from pathlib import Path
# --- error: decodificación implícita ---
def read_config_legacy(path: str) -> str:
with open(path) as f: # modo texto sin encoding= en casos límite de Windows/locale
return f.read()
# --- corrección: UTF-8 explícito ---
def read_config(path: str) -> str:
return Path(path).read_text(encoding="utf-8")
# --- error: mezcla de str y bytes ---
def broken_join(prefix: str, payload: bytes) -> bytes:
return prefix + payload # TypeError
# --- corrección: codificar en el límite ---
def fixed_join(prefix: str, payload: bytes) -> bytes:
return prefix.encode("utf-8") + payload
# --- error: comparación sin normalización ---
def usernames_equal(a: str, b: str) -> bool:
return a == b
# --- corrección: normalización NFC para identificadores de usuario ---
def usernames_equal_normalized(a: str, b: str) -> bool:
return unicodedata.normalize("NFC", a.casefold()) == unicodedata.normalize("NFC", b.casefold())
# --- manejo seguro de bytes desconocidos ---
def decode_best_effort(raw: bytes) -> str:
return raw.decode("utf-8", errors="replace")
# demostración
assert decode_best_effort(b"ok\xc3\xa9") == "oké"
assert usernames_equal_normalized("café", "café") # formas visualmente igualesLo que esto demuestra:
- Los archivos de texto se abren con
encoding="utf-8"explícito - Codificar
strabytesantes de la concatenación binaria unicodedata.normalizemáscasefoldpara una igualdad de cadenas robustaerrors="replace"para registrar datos sucios sin fallar
Profundización
Cómo funciona
strson puntos de código Unicode;bytesson octetos crudos- Las codificaciones (UTF-8, Latin-1) mapean bytes ↔ str en los límites de E/S
- El código fuente de Python 3 es UTF-8 por defecto (PEP 3120)
- Las formas de normalización (NFC, NFD) alinean secuencias canónicamente equivalentes
Puntos de fallo comunes
| Ubicación | Error | Corrección |
|---|---|---|
open() | codificación de locale por defecto | encoding="utf-8" |
| HTTP | Content-Type charset incorrecto | decodificar según la cabecera |
| JSON | json.dumps a bytes manualmente | usar JSON str, codificar una vez |
| Nombres de archivo | rutas de bytes no decodificadas | os.fsdecode / pathlib |
| DB | columnas Latin-1 heredadas | migrar a UTF-8 o decodificar explícitamente |
Notas de Python
# subprocess: modo texto con codificación explícita (3.14)
import subprocess
subprocess.run(["echo", "hi"], capture_output=True, text=True, encoding="utf-8")
# sqlite almacena TEXT como str Unicode cuando se usa correctamente la fábrica de textoTrampas
- Doble codificación - Bytes UTF-8 decodificados como Latin-1 y luego guardados como UTF-8 producen mojibake. Corrección: identificar los bytes originales; volver a decodificar desde la fuente.
- Adivinación de
chardeten producción - una suposición incorrecta corrompe los datos silenciosamente. Corrección: corregir la codificación upstream; usarerrors="strict"en la ingesta. - Normalización de contraseñas - cambia la entrada del usuario. Corrección: normalizar identificadores/visualización, no secretos.
- Comparación de resúmenes hash de cadenas sin codificación -
hash(s)no es para criptografía;hashlibnecesita.encode("utf-8"). - Emojis e índices de bases de datos heredadas - los límites de longitud de intercalación rompen el almacenamiento. Corrección: UTF-8 MB4 en la configuración de MySQL/Postgres.
- Lectura de archivos binarios como texto - PDF/PNG abiertos en modo texto. Corrección: modo binario + analizar el formato.
Alternativas
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
| UTF-8 en todas partes | servicios modernos | codificaciones heredadas obligatorias |
errors="replace" | ingesta de registros | registros brutos financieros/legales |
| IDNA/punycode | nombres de dominio | almacenamiento general de texto |
bytes de extremo a extremo | trabajo criptográfico/protocolo | texto visible para el usuario sin plan de decodificación |
Preguntas frecuentes
¿Debo usar utf-8-sig?
Usar para archivos de Excel/Windows con BOM. La mayoría de las API y JSON usan UTF-8 plano sin BOM.
¿Cuál es la diferencia entre NFC y NFD?
NFC compone caracteres (é como un punto de código); NFD descompone (e + acento combinatorio). Elige una forma en el almacenamiento y normaliza en la entrada.
¿Cómo arreglo datos de base de datos Latin-1?
Identificar columnas, migrar el esquema a UTF-8, convertir bytes con la codificación de origen conocida en una ventana de mantenimiento, verificar con muestras de suma de verificación.
¿requests maneja la codificación automáticamente?
response.text adivina a partir de las cabeceras; establece response.encoding o usa response.content.decode("utf-8") explícitamente cuando las cabeceras mienten.
¿Por qué casefold en lugar de lower()?
casefold maneja más casos especiales de Unicode (por ejemplo, la ß alemana) para la comparación insensible a mayúsculas y minúsculas de cadenas visibles para el usuario.
¿Son las expresiones regulares conscientes de Unicode?
Usa el valor por defecto re.UNICODE en Python 3; añade (?u) o clases de caracteres como \w sabiendo que coinciden con letras Unicode.
¿Cómo manejan las rutas pathlib el Unicode?
Path usa str en Unix y APIs Unicode en Windows. Evita pasar bytes no decodificados a open en Windows.
¿Qué pasa con base64 y el texto?
Base64 codifica bytes. Decodifica base64 a bytes, luego decodifica bytes a str con el códec de texto correcto.
¿Puede JSON contener no UTF-8?
JSON es Unicode; json.dumps devuelve str. Codifica a bytes UTF-8 solo en el límite de red/almacenamiento.
¿Cómo depuro mojibake?
Inspecciona los bytes crudos con repr(raw) y prueba la decodificación correcta. Compara el hex de UTF-8 esperado (por ejemplo, c3 a9 para é).
¿Cambian los valores predeterminados de codificación en Python 3.14?
El modo UTF-8 (PYTHONUTF8=1) y encoding= explícito siguen siendo la mejor práctica en todas las versiones compatibles, incluida la 3.14.0.
¿Cuándo está bien errors=ignore?
Raramente. Aceptable para la visualización con pérdida de registros de terceros, nunca para datos que persistes o facturas.
Relacionado
- Migración de una aplicación heredada de Python 2/Django/Flask - Migración de Unicode de Py2
- Errores de punto flotante y numéricos - clase diferente de errores de datos
- Defectos en canalizaciones de datos - codificación CSV en ETL
- Fragmentos de refactorización para depuración - correcciones de bytes/str
- Herramientas de depuración - inspeccionar bytes crudos en pdb
Versiones de la pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+ y uv 0.6+.