open - Lectura de datos de archivo Unicode con caracteres BOM en Python
python unicode utf-8 (6)
Encuentro las otras respuestas demasiado complejas. Hay una manera más simple que no necesita caer en el idioma de bajo nivel de E / S de archivos binarios, no depende de una heurística de conjunto de caracteres ( chardet
) que no es parte de la biblioteca estándar de Python, y no lo hace necesita una firma de codificación alternativa rara vez vista ( utf-8-sig
frente a la común utf-8
) que no parece tener un análogo en la familia UTF-16.
El enfoque más simple que he encontrado es tratar con los caracteres de la BOM en Unicode y dejar que los códecs hagan el trabajo pesado. Solo hay una marca de orden de bytes Unicode, por lo que una vez que los datos se convierten en caracteres Unicode, es fácil determinar si está allí y / o agregarlos / eliminarlos. Para leer un archivo con una posible lista de materiales:
BOM = ''/ufeff''
with open(filepath, mode=''r'', encoding=''utf-8'') as f:
text = f.read()
if text.startswith(BOM):
text = text[1:]
Esto funciona con todos los códecs UTF interesantes (por ejemplo, utf-8
, utf-16le
, utf-16be
, ...), no requiere módulos adicionales, y no requiere caer en el procesamiento de archivos binarios o constantes de codec
específicas.
Para escribir una lista de materiales:
text_with_BOM = text if text.startswith(BOM) else BOM + text
with open(filepath, mode=''w'', encoding=''utf-16be'') as f:
f.write(text_with_BOM)
Esto funciona con cualquier codificación. UTF-16 big endian es solo un ejemplo.
Esto no es, por cierto, para despedir a chardet
. Puede ser útil cuando no tienes información sobre la codificación que utiliza un archivo. Simplemente no es necesario para agregar / eliminar listas de materiales.
Estoy leyendo una serie de archivos de código fuente usando Python y ejecutando un error BOM unicode. Aquí está mi código:
bytes = min(32, os.path.getsize(filename))
raw = open(filename, ''rb'').read(bytes)
result = chardet.detect(raw)
encoding = result[''encoding'']
infile = open(filename, mode, encoding=encoding)
data = infile.read()
infile.close()
print(data)
Como puede ver, estoy detectando la codificación usando chardet
, luego leyendo el archivo en la memoria e intentando imprimirlo. La sentencia de impresión falla en los archivos Unicode que contienen una lista de materiales con el error:
UnicodeEncodeError: el códec ''charmap'' no puede codificar caracteres en la posición 0-2:
mapas de caracteres a <undefined>
Supongo que está intentando decodificar la lista de materiales utilizando el conjunto de caracteres predeterminado y está fallando. ¿Cómo elimino la lista de materiales de la cadena para evitar esto?
He compuesto un ingenioso detector basado en BOM basado en la respuesta de Chewie. Es suficiente en el caso de uso común donde los datos pueden estar en una codificación local conocida o Unicode con BOM (eso es lo que típicamente producen los editores de texto). Más importante aún, a diferencia de chardet
, no hace ninguna adivinanza al azar, por lo que da resultados predecibles:
def detect_by_bom(path,default):
with open(path, ''rb'') as f:
raw = f.read(4) #will read less if the file is smaller
for enc,boms in /
(''utf-8-sig'',(codecs.BOM_UTF8,)),/
(''utf-16'',(codecs.BOM_UTF16_LE,codecs.BOM_UTF16_BE)),/
(''utf-32'',(codecs.BOM_UTF32_LE,codecs.BOM_UTF32_BE)):
if any(raw.startswith(bom) for bom in boms): return enc
return default
Los caracteres de la BOM deben eliminarse automáticamente al decodificar UTF-16, pero no UTF-8, a menos que use explícitamente la codificación utf-8-sig
. Podrías probar algo como esto:
import io
import chardet
import codecs
bytes = min(32, os.path.getsize(filename))
raw = open(filename, ''rb'').read(bytes)
if raw.startswith(codecs.BOM_UTF8):
encoding = ''utf-8-sig''
else:
result = chardet.detect(raw)
encoding = result[''encoding'']
infile = io.open(filename, mode, encoding=encoding)
data = infile.read()
infile.close()
print(data)
No hay ninguna razón para comprobar si existe una lista de materiales o no, utf-8-sig
gestiona y se comporta exactamente como utf-8
si la lista de materiales no existe:
# Standard UTF-8 without BOM
>>> b''hello''.decode(''utf-8'')
''hello''
>>> b''hello''.decode(''utf-8-sig'')
''hello''
# BOM encoded UTF-8
>>> b''/xef/xbb/xbfhello''.decode(''utf-8'')
''/ufeffhello''
>>> b''/xef/xbb/xbfhello''.decode(''utf-8-sig'')
''hello''
En el ejemplo anterior, puede ver que utf-8-sig
decodifica correctamente la cadena dada independientemente de la existencia de la lista de materiales. Si cree que hay incluso una pequeña posibilidad de que exista un carácter BOM en los archivos que está leyendo, simplemente use utf-8-sig
y no se preocupe por ello.
Una variante de la respuesta de @ ivan_pozdeev para cadenas / excepciones (en lugar de archivos). Estoy tratando con el contenido HTML unicode que estaba rellenado en una excepción de Python (ver http://bugs.python.org/issue2517 )
def detect_encoding(bytes_str):
for enc, boms in /
(''utf-8-sig'',(codecs.BOM_UTF8,)),/
(''utf-16'',(codecs.BOM_UTF16_LE,codecs.BOM_UTF16_BE)),/
(''utf-32'',(codecs.BOM_UTF32_LE,codecs.BOM_UTF32_BE)):
if (any(bytes_str.startswith(bom) for bom in boms): return enc
return ''utf-8'' # default
def safe_exc_to_str(exc):
try:
return str(exc)
except UnicodeEncodeError:
return unicode(exc).encode(detect_encoding(exc.content))
Alternativamente, este código mucho más simple puede eliminar caracteres no ascii sin mucho alboroto:
def just_ascii(str):
return unicode(str).encode(''ascii'', ''ignore'')
chardet
detecta BOM_UTF8 automáticamente desde la versión 2.3.0 lanzada el 7 de octubre de 2014 :
#!/usr/bin/env python
import chardet # $ pip install chardet
# detect file encoding
with open(filename, ''rb'') as file:
raw = file.read(32) # at most 32 bytes are returned
encoding = chardet.detect(raw)[''encoding'']
with open(filename, encoding=encoding) as file:
text = file.read()
print(text)
Nota: chardet
puede devolver ''UTF-XXLE''
, ''UTF-XXBE''
que dejan la lista de materiales en el texto. ''LE''
, ''BE''
deben quitarse para evitarlo, aunque es más fácil detectar la BOM usted mismo en este punto, por ejemplo, como en la respuesta de @ ivan_pozdeev .
Para evitar UnicodeEncodeError
al imprimir texto Unicode en la consola de Windows, consulte Python, Unicode y la consola de Windows .