我正在使用Python阅读一系列源代码文件并遇到unicode BOM错误。这是我的代码:
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)
如您所见,我正在使用chardet
检测编码,然后在内存中读取文件并尝试打印它。包含BOM的Unicode文件的print语句失败,错误为:
UnicodeEncodeError:'charmap'编解码器无法对位置0-2中的字符进行编码:
字符映射到< undefined>
我猜它正在尝试使用默认字符集解码BOM并且它失败了。如何从字符串中删除BOM以防止这种情况?
答案 0 :(得分:50)
除非您明确使用utf-8-sig
编码,否则在解码UTF-16时,应自动剥离BOM字符,而不是UTF-8。你可以尝试这样的事情:
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)
答案 1 :(得分:28)
没有理由检查BOM是否存在,utf-8-sig
为您管理,并且如果BOM不存在则行为与utf-8
完全相同:
# 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'
在上面的示例中,无论BOM是否存在,您都可以看到utf-8-sig
正确解码给定的字符串。如果您认为您正在阅读的文件中可能存在BOM字符的可能性很小,那么只需使用utf-8-sig
而不用担心它
答案 2 :(得分:18)
我根据Chewie的答案编写了一个漂亮的基于BOM的探测器。在通用用例中,数据可以是已知的本地编码,也可以是带有BOM的Unicode(这是文本编辑通常生成的)。更重要的是,与chardet
不同,它不会进行任何随机猜测,因此它可以提供可预测的结果:
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
答案 3 :(得分:8)
chardet
detects BOM_UTF8 automatically以来2.3.0 version released on Oct 7, 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)
注意:chardet
可能会返回'UTF-XXLE'
,'UTF-XXBE'
编码,将BOM留在文本中。 'LE'
,'BE'
应该被删除以避免它 - 尽管此时您自己更容易检测到BOM,例如@ivan_pozdeev's answer。
要在将Unicode文本打印到Windows控制台时避免UnicodeEncodeError
,请参阅Python, Unicode, and the Windows console。
答案 4 :(得分:6)
我发现其他答案过于复杂。有一种更简单的方法,不需要深入到二进制文件I / O的低级习语,不依赖于字符集启发式(chardet
)不是Python标准库的一部分,并且不需要很少看到的替代编码签名(utf-8-sig
与普通utf-8
)似乎没有模拟UTF-16系列。
我发现最简单的方法是处理Unicode中的BOM字符,并让编解码器完成繁重工作。只有一个Unicode byte order mark,因此一旦数据转换为Unicode字符,确定它是否存在和/或添加/删除它很容易。要读取带有可能BOM的文件:
BOM = '\ufeff'
with open(filepath, mode='r', encoding='utf-8') as f:
text = f.read()
if text.startswith(BOM):
text = text[1:]
这适用于所有有趣的UTF编解码器(例如utf-8
,utf-16le
,utf-16be
,...),不需要额外的模块,也不需要额外的模块。需要降低到二进制文件处理或特定codec
常量。
编写BOM:
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)
这适用于任何编码。 UTF-16大端只是一个例子。
这不是解雇chardet
。当您没有信息文件的编码使用时,它可以提供帮助。只需添加/删除BOM即可。
答案 5 :(得分:1)
@ ivan_pozdeev对字符串/异常(而不是文件)的回答。我正在处理填充在python异常中的unicode HTML内容(参见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))
或者,这个更简单的代码能够毫不费力地删除非ascii字符:
def just_ascii(str):
return unicode(str).encode('ascii', 'ignore')