python读取文件utf-8解码问题

python读取文件utf-8解码问题,python,python-3.x,Python,Python 3.x,我在读取具有UTF8和ASCII字符的文件时遇到问题。问题是我使用seek只读取部分数据,但我不知道我是否在UTF8的“中间”读取 osx python 3.6.6 简单地说,我的问题可以用下面的代码演示 # write some utf-8 to a file open('/tmp/test.txt', 'w').write(chr(12345)+chr(23456)+chr(34567)+'\n') data = open('/tmp/test.txt') data.read() # t

我在读取具有UTF8和ASCII字符的文件时遇到问题。问题是我使用seek只读取部分数据,但我不知道我是否在UTF8的“中间”读取

  • osx
  • python 3.6.6
简单地说,我的问题可以用下面的代码演示

# write some utf-8 to a file
open('/tmp/test.txt', 'w').write(chr(12345)+chr(23456)+chr(34567)+'\n')
data = open('/tmp/test.txt')
data.read() # this works fine. to just demo I can read the file as whole
data.seek(1)
data.read(1) # UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte
# I can read by seek 3 by 3
data.seek(3)
data.read(1) # this works fine. 
我知道我可以用二进制打开文件,然后通过查找到任何位置来读取它,而不会产生任何问题,但是,我需要处理字符串,所以当解码为字符串时,我会遇到同样的问题

data = open('/tmp/test.txt', 'rb')
data.seek(1)
z = data.seek(3)
z.decode() # will hit same error 
不使用seek,我甚至可以调用read(1)来正确读取它

我能想到的一件事是,在搜索到一个位置后,尝试读取UnicodeDecodeError上的position=position-1,seek(position),直到我能正确读取它


是否有更好的(正确的)处理方法?

如文档所述,在处理文本文件时:

偏移量必须是由
TextIOBase.tell()返回的数字,或者是零。任何其他偏移值都会产生未定义的行为

实践中,<代码>查找(1)实际上是在文件中查找1字节,将其放在字符的中间。因此,最终发生的情况与此类似:

>>> s = chr(12345)+chr(23456)+chr(34567)+'\n'
>>> b = s.encode()
>>> b
b'\xe3\x80\xb9\xe5\xae\xa0\xe8\x9c\x87\n'
>>> b[1:]
b'x80\xb9\xe5\xae\xa0\xe8\x9c\x87\n'
>>> b[1:].decode()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb9 in position 3: invalid start byte
因此,
seek(3)
碰巧起作用,尽管这是不合法的,因为你恰好在寻找一个角色的开头。这相当于:

>>> b[3:].decode()
'宠蜇\n'

如果您想依靠这种未记录的行为来尝试随机搜索UTF-8文本文件的中间部分,通常可以按照您的建议进行操作。例如:

def readchar(f, pos):
    for i in range(pos:pos+5):
        try:
            f.seek(i)
            return f.read(1)
        except UnicodeDecodeError:
            pass
    raise UnicodeDecodeError('Unable to find a UTF-8 start byte')
或者,您可以使用的知识手动扫描二进制文件中的有效起始字节:

def readchar(f, pos):
    f.seek(pos)
    for _ in range(5):
        byte = f.read(1)
        if byte in range(0, 0x80) or byte in range(0xC0, 0x100):
            return byte
    raise UnicodeDecodeError('Unable to find a UTF-8 start byte')

但是,如果您实际上只是在某个任意点之前或之后寻找下一条完整的线,那么这就容易多了

在UTF-8中,换行符编码为单个字节,与ASCII中相同的字节即
'\n'
编码为
b'\n'
。(如果有Windows样式的结尾,return也是如此,因此
'\r\n'
也会编码到
b'\r\n'
)这是经过设计的,以便更容易处理此类问题

因此,如果以二进制模式打开文件,可以向前或向后搜索,直到找到换行字节。然后,您可以使用(二进制文件)
readline
方法从那里读取,直到下一个换行符

具体细节取决于您希望在此处使用的规则。另外,我将展示一个愚蠢的,完全未优化的版本,一次读取一个字符;在现实生活中,您可能希望备份、读取和扫描(例如,使用
rfind
),比如一次80个字符,但这可能更容易理解:

def getline(f, pos, maxpos):
    for start in range(pos-1, -1, -1):
        f.seek(start)
        if f.read(1) == b'\n':
            break
    else:
        f.seek(0)
    return f.readline().decode()
这就是它的作用:

>>> s = ''.join(f'{i}:\u3039\u5ba0\u8707\n' for i in range(5))
>>> b = s.encode()
>>> f = io.BytesIO(b)
>>> maxlen = len(b)
>>> print(getline(f, 0, maxlen))
0:〹宠蜇
>>> print(getline(f, 1, maxlen))
0:〹宠蜇
>>> print(getline(f, 10, maxlen))
0:〹宠蜇
>>> print(getline(f, 11, maxlen))
0:〹宠蜇
>>> print(getline(f, 12, maxlen))
1:〹宠蜇
>>> print(getline(f, 59, maxlen))
4:〹宠蜇

如文档所述,当您在文本文件上执行以下操作时:

偏移量必须是由
TextIOBase.tell()返回的数字,或者是零。任何其他偏移值都会产生未定义的行为

实践中,<代码>查找(1)实际上是在文件中查找1字节,将其放在字符的中间。因此,最终发生的情况与此类似:

>>> s = chr(12345)+chr(23456)+chr(34567)+'\n'
>>> b = s.encode()
>>> b
b'\xe3\x80\xb9\xe5\xae\xa0\xe8\x9c\x87\n'
>>> b[1:]
b'x80\xb9\xe5\xae\xa0\xe8\x9c\x87\n'
>>> b[1:].decode()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb9 in position 3: invalid start byte
因此,
seek(3)
碰巧起作用,尽管这是不合法的,因为你恰好在寻找一个角色的开头。这相当于:

>>> b[3:].decode()
'宠蜇\n'

如果您想依靠这种未记录的行为来尝试随机搜索UTF-8文本文件的中间部分,通常可以按照您的建议进行操作。例如:

def readchar(f, pos):
    for i in range(pos:pos+5):
        try:
            f.seek(i)
            return f.read(1)
        except UnicodeDecodeError:
            pass
    raise UnicodeDecodeError('Unable to find a UTF-8 start byte')
或者,您可以使用的知识手动扫描二进制文件中的有效起始字节:

def readchar(f, pos):
    f.seek(pos)
    for _ in range(5):
        byte = f.read(1)
        if byte in range(0, 0x80) or byte in range(0xC0, 0x100):
            return byte
    raise UnicodeDecodeError('Unable to find a UTF-8 start byte')

但是,如果您实际上只是在某个任意点之前或之后寻找下一条完整的线,那么这就容易多了

在UTF-8中,换行符编码为单个字节,与ASCII中相同的字节即
'\n'
编码为
b'\n'
。(如果有Windows样式的结尾,return也是如此,因此
'\r\n'
也会编码到
b'\r\n'
)这是经过设计的,以便更容易处理此类问题

因此,如果以二进制模式打开文件,可以向前或向后搜索,直到找到换行字节。然后,您可以使用(二进制文件)
readline
方法从那里读取,直到下一个换行符

具体细节取决于您希望在此处使用的规则。另外,我将展示一个愚蠢的,完全未优化的版本,一次读取一个字符;在现实生活中,您可能希望备份、读取和扫描(例如,使用
rfind
),比如一次80个字符,但这可能更容易理解:

def getline(f, pos, maxpos):
    for start in range(pos-1, -1, -1):
        f.seek(start)
        if f.read(1) == b'\n':
            break
    else:
        f.seek(0)
    return f.readline().decode()
这就是它的作用:

>>> s = ''.join(f'{i}:\u3039\u5ba0\u8707\n' for i in range(5))
>>> b = s.encode()
>>> f = io.BytesIO(b)
>>> maxlen = len(b)
>>> print(getline(f, 0, maxlen))
0:〹宠蜇
>>> print(getline(f, 1, maxlen))
0:〹宠蜇
>>> print(getline(f, 10, maxlen))
0:〹宠蜇
>>> print(getline(f, 11, maxlen))
0:〹宠蜇
>>> print(getline(f, 12, maxlen))
1:〹宠蜇
>>> print(getline(f, 59, maxlen))
4:〹宠蜇

随机读取字节确实不会使事情变成UTF-8。你的实际目标是什么,你正试图做到这一点?如果你想向前或向后移动n个代码点,你可能需要扫描整个字节(可能有一个包为你这样做)。随机读取字节确实不会使事情变得UTF-8。你的实际目标是什么,你正试图做到这一点?如果您想向前或向后移动n个代码点,您可能需要扫描整个字节(可能有一个包为您这样做)。谢谢。utf编码范围是一个很大的帮助。所以,我可以“测试”看我是否在正确的位置上。我正在进行“随机”搜索,因为我需要从大文件(大小以T为单位)中获取一些数据样本,例如,从文件中随机获取100行。@RuiLi如果您要查找随机行,这会容易得多;这就是为什么。让我更新答案以帮助更多。感谢您的详细解释。即使是我最初的问题也没有详细说明我是如何使用这段代码的,但我认为这仍然是值得的。我学到的不仅仅是计数\n。我知道我可以在二进制模式下\n计数,今天我正在阅读“整行”。了解如何处理utf8也将在将来帮助我,如果我