使用Python中的BOM表读取Unicode文件数据

Chr*_*ris 38 python unicode

我正在使用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)
Run Code Online (Sandbox Code Playgroud)

如您所见,我正在检测编码chardet,然后在内存中读取文件并尝试打印它.包含BOM的Unicode文件的print语句失败,错误如下:

UnicodeEncodeError:'charmap'编解码器无法对位置0-2中的
字符进行编码:字符映射到<undefined>

我猜它正在尝试使用默认字符集解码BOM并且它失败了.如何从字符串中删除BOM以防止这种情况?

Che*_*wie 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)
Run Code Online (Sandbox Code Playgroud)

  • 有趣的是,`chardet`不会自动执行此操作. (10认同)

lig*_*h05 41

没有理由检查BOM是否存在utf-8-sig,为您管理,并且行为utf-8与BOM不存在完全相同:

# 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'
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,utf-8-sig无论BOM是否存在,您都可以正确地看到给定字符串的解码.如果您认为您正在阅读的文件中存在BOM字符的可能性很小,请使用utf-8-sig而不用担心

  • 直到现在我还没有遇到 utf-8-sig --- 谢谢!为什么剥离 BOM 不是更明显的编码值的默认行为?我的意思是,有人真的想在从文本文件中读取的字符串中查看 BOM 吗? (2认同)
  • @AdamF 我认为 `utf-8` 和 `utf-8-sig` 之间的想法是没有意外的行为/魔法。我很高兴 Python `utf-8` 按原样解码文件,BOM 是文件中的一个字符,因此保留它是有意义的。我也很高兴`utf-8-sig` 自动处理剥离它。虽然我不知道有人想要 BOM 的情况,但我确信存在用例。通过这两种编码,我们可以决定自己的预期行为。 (2认同)

iva*_*eev 21

我根据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
Run Code Online (Sandbox Code Playgroud)

  • 此代码片段应以相反的顺序检查 BOM,因为 UTF-16 的 BOM 是 UTF-32 的 BOM 子集(因此这会将 UTF-32 编码的字符串错误识别为 UTF-16)。即,参见https://github.com/umit-ozturk/Ansible-Example-AB2018/blob/1a238a27a61fb2c076c00cdeb9037e131e874f8a/lib/python3.6/site-packages/pip/_vendor/html5lib/_inputstream.py#L535-L567 (2认同)

jfs*_*jfs 8

chardet2014年10月7日发布2.3.0版本后自动检测BOM_UTF8:

#!/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)
Run Code Online (Sandbox Code Playgroud)

注:chardet可能会返回'UTF-XXLE','UTF-XXBE'即离开BOM的文本编码.'LE','BE'应该被剥夺,以避免它-虽然它是更容易发现自己BOM此时例如,如@ ivan_pozdeev的答案.

要避免UnicodeEncodeError在将Unicode文本打印到Windows控制台时,请参阅Python,Unicode和Windows控制台.


Jon*_*ice 7

我发现其他答案过于复杂.有一种更简单的方法,不需要下降到二进制文件I/O的低级习语,不依赖于chardet不属于Python标准库的字符集heuristic(),并且不需要一个很少见的备用编码签名(utf-8-sig相对于常见的utf-8),似乎在UTF-16系列中没有模拟.

我发现最简单的方法是处理Unicode中的BOM字符,并让编解码器完成繁重的工作.只有一个Unicode 字节顺序标记,因此一旦数据转换为Unicode字符,确定它是否存在和/或添加/删除它很容易.要读取带有可能BOM的文件:

BOM = '\ufeff'
with open(filepath, mode='r', encoding='utf-8') as f:
    text = f.read()
    if text.startswith(BOM):
        text = text[1:]
Run Code Online (Sandbox Code Playgroud)

这适用于所有有趣的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)
Run Code Online (Sandbox Code Playgroud)

这适用于任何编码.UTF-16大端只是一个例子.

顺便说一句,这不是解雇chardet.当您没有信息文件的编码使用时,它可以提供帮助.只是不需要添加/删除BOM.