Un script de Python que funciona sin problemas en otra máquina falla en Windows al imprimir texto con emojis o símbolos:
Traceback (most recent call last):
File "informe.py", line 3, in <module>
print("\U0001F550 \u2264")
File "...\Lib\encodings\cp1252.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: 'charmap' codec can't encode character '\U0001f550' in position 0: character maps to <undefined>
El carácter puede ser otro (por ejemplo '\u2264', el símbolo ≤). Con la misma raíz, leer un archivo JSON con open() falla en sentido contrario:
UnicodeDecodeError: 'charmap' codec can't decode byte 0x90 in position 15: character maps to <undefined>
Los datos no tienen nada malo: el JSON es válido y el texto que quieres enviar a otro sistema también. Falla solo el paso local de leer del disco o escribir en la salida estándar con una codificación que no soporta esos caracteres.
Por qué pasa
Para convertir texto en bytes (y al revés) Python necesita una codificación. Si no se le indica una, usa la del sistema. En muchas instalaciones de Windows en español, esa codificación es cp1252, que no contiene emojis ni símbolos como ≤. El nombre charmap del error viene del códec que implementa cp1252, no de tu código.
Hay dos puntos donde ocurre:
- Archivos. En Python 3.14 y anteriores,
open()sinencodingusa la codificación local. Un archivo escrito en UTF-8 se lee entonces como cp1252: algunos bytes (como0x90) no existen en esa tabla y producenUnicodeDecodeError; el resto se decodifica sin error, pero mal. Por ejemplo,canciónse lee comocanción. - Salida estándar. Desde Python 3.6, la consola interactiva de Windows usa UTF-8, pero la documentación aclara que ese cambio solo se aplica a la consola interactiva, no cuando la salida se redirige a un archivo o a una tubería. Si el mismo script se ejecuta desde una tarea programada, un servicio, un sistema de CI, con
>o|, o desde un terminal que entrega una tubería en lugar de una consola,print()vuelve a usar la codificación local.
Esto cambia en Python 3.15: según la documentación, el modo UTF-8 pasa a estar activado por defecto (PEP 686) y open() usa UTF-8 si no se indica encoding. En 3.14 y versiones anteriores el problema sigue vigente.
Solución
-
Comprueba qué codificación está usando tu Python.
import locale, sys print(sys.version.split()[0], sys.stdout.encoding, locale.getpreferredencoding(False), sys.flags.utf8_mode)Si ejecutas esto con la salida redirigida (por ejemplo, añadiendo
| moreal comando de Python) y vescp1252y0, estás en el caso descrito. -
En archivos, indica siempre la codificación. Es la solución más robusta porque no depende del equipo donde se ejecute:
import json with open("datos.json", encoding="utf-8") as f: datos = json.load(f) with open("salida.json", "w", encoding="utf-8") as f: json.dump(datos, f, ensure_ascii=False, indent=2)Pasar
encodingde forma explícita hace que el código se comporte igual en todas las versiones de Python y en todos los equipos. -
En la salida estándar, elige una de estas opciones.
- Dentro del script, al inicio (disponible desde Python 3.7):
import sys sys.stdout.reconfigure(encoding="utf-8") - Con una variable de entorno, solo para la salida estándar. En PowerShell, para la sesión actual:
En$env:PYTHONIOENCODING = 'utf-8' python informe.pycmdesset PYTHONIOENCODING=utf-8. Para dejarla definida en tu usuario puedes usarsetx PYTHONIOENCODING utf-8; solo afecta a los procesos que abras después. - Con el modo UTF-8 de Python (3.7 o posterior), que además hace que
open()use UTF-8 por defecto:
Equivale a definirpython -X utf8 informe.pyPYTHONUTF8=1.
- Dentro del script, al inicio (disponible desde Python 3.7):
-
En una tarea programada o un servicio, configura el entorno de esa tarea. No hereda el entorno de tu terminal. La forma más simple es poner la opción en el propio comando, por ejemplo
python -X utf8 C:\proyectos\mi-app\informe.py, o definir la variable de entorno en la configuración de la tarea o del servicio.
Elegir entre PYTHONIOENCODING y el modo UTF-8 es una cuestión de alcance. PYTHONIOENCODING solo cambia stdin, stdout y stderr. El modo UTF-8 también cambia la codificación por defecto de open(), por lo que un script que lea archivos antiguos en cp1252 sin indicar encoding pasaría a leerlos mal. Si tienes archivos así, indícalo en cada open() con encoding="cp1252".
Cómo verificar que quedó resuelto
Prueba siempre con la salida redirigida, porque en la consola interactiva el error puede no aparecer ni siquiera antes de la corrección:
python informe.py | more
python -c "import json; print(json.load(open('datos.json', encoding='utf-8')))" | more
El primer comando debe imprimir todo el texto, con emojis y símbolos, sin Traceback. El segundo debe cargar el JSON. Para confirmar la codificación de la salida:
python -X utf8 -c "import sys; print(sys.stdout.encoding)" | more
Debe mostrar utf-8. Si el script se ejecuta como tarea programada o servicio, revisa su registro después de la primera ejecución real.
Variantes y errores relacionados
Error al escribir un archivo
Un open("salida.txt", "w") sin encoding produce el mismo UnicodeEncodeError al escribir texto que cp1252 no contiene. La solución es la del paso 2.
UnicodeDecodeError: 'utf-8' codec can't decode byte ...
Es el caso inverso: el archivo no está en UTF-8, sino en cp1252 o en otra codificación, y lo abres como UTF-8. Indica la codificación real (encoding="cp1252") o convierte el archivo a UTF-8.
PYTHONIOENCODING con manejador de errores
La sintaxis es codificación:manejador. Por ejemplo, PYTHONIOENCODING=cp1252:replace evita la excepción sustituyendo cada carácter no representable por ?. Es útil para que un script no se detenga, pero pierde información: no lo uses si necesitas conservar el texto.
La variable no cambia nada en la consola interactiva
En Windows, PYTHONIOENCODING se ignora en los búferes de la consola interactiva (salvo que también se defina PYTHONLEGACYWINDOWSSTDIO); sí afecta a archivos y tuberías redirigidos. Por eso conviene probar con la salida redirigida, como se explica arriba.
Python 3.15 o posterior
Con el modo UTF-8 activado por defecto, el código sin encoding lee y escribe UTF-8. Si necesitas el comportamiento anterior, puedes desactivarlo con PYTHONUTF8=0 o -X utf8=0, o pedir la codificación local de forma explícita con encoding="locale" (disponible desde Python 3.10).
Resumen
'charmap' codec can't encode/decodeen Windows suele significar que Python usó cp1252 por defecto; los datos no están dañados.- Al leer o escribir archivos, pasa siempre
encoding="utf-8"aopen(). - Para la salida, usa
sys.stdout.reconfigure(encoding="utf-8"),PYTHONIOENCODING=utf-8opython -X utf8(equivale aPYTHONUTF8=1). - El fallo aparece sobre todo con la salida redirigida (tareas, servicios, CI, tuberías); en tareas y servicios configura la opción en su propio entorno.
- Desde Python 3.15 el modo UTF-8 es el predeterminado; en 3.14 y anteriores sigue haciendo falta lo anterior.
Comentarios
Publicar un comentario