如何从 Python 中的文件中读取单个 UTF-8 字符?

How to read a single UTF-8 character from a file in Python?

f.read(1) 将 return 1 个字节,而不是一个字符。该文件是二进制文件,但文件中的特定范围是 UTF-8 编码的字符串,长度在字符串之前。字符串末尾没有换行符。我如何读取这些字符串?

我看过 this question,但 none 的答案涉及 UTF-8 案例。

示例代码:

file = 'temp.txt'
with open(file, 'wb') as f:
    f.write(b'\x41')
    f.write(b'\xD0')
    f.write(b'\xB1')
    f.write(b'\xC0')

with open(file, 'rb') as f:
    print(f.read(1), '+', f.read(1))
with open(file, 'r') as f:
    print(f.buffer.read(1), '+', f.read(1))

这输出:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc0 in position 2: invalid start byte

删除 f.write(b'\xC0') 后,它会按预期工作。似乎读到的比它被告知的要多:代码没有说要读取 0xC0 字节。

这里是一个字符,占用了一个以上的字节。不管你打开的文件是否是 utf-8 编码,读取一个字节似乎就可以完成工作并且你得到了整个字符。

file = 'temp.txt'
with open(file, 'wb') as f:
    f.write('⾀'.encode('utf-8'))
    f.write(b'\x01')
    
with open(file, 'rb') as f:
    print(f.read(1))
with open(file, 'r') as f:
    print(f.read(1))

输出:

b'\xe2'
⾀

即使部分文件不是utf-8,您仍然可以以阅读模式(非二进制)打开文件,跳到您要阅读的字节,然后通过[=24阅读整个字符=] read(1).

即使您的角色不在文件的开头,这仍然有效:

file = 'temp.txt'
with open(file, 'wb') as f:
    f.write(b'\x01')
    f.write('⾀'.encode('utf-8'))

    
with open(file, 'rb') as f:
    print(f.read(1), '+', f.read(1))
with open(file, 'r') as f:
    print(f.read(1),'+', f.read(1))

如果这对您不起作用,请提供示例。

The file is binary but particular ranges in the file are UTF-8 encoded strings with the length coming before the string.

你有字符串的长度,这可能是 byte 长度,因为它在二进制文件中最有意义。以二进制模式读取字节范围并在事后对其进行解码。这是一个使用 UTF-8 字符串编写二进制文件的人为示例,其中长度首先编码。它有两个字节的长度,后跟编码的字符串数据,每边有 10 个字节的随机数据。

import os
import struct

string = "我不喜欢你女朋友。你需要一个新的。"

with open('sample.bin','wb') as f:
    f.write(os.urandom(10))  # write 10 random bytes
    encoded = string.encode()
    f.write(len(encoded).to_bytes(2,'big')) # write a two-byte big-endian length
    f.write(encoded)                        # write string
    f.write(os.urandom(10))                 # 10 more random bytes

with open('sample.bin','rb') as f:
    print(f.read())  # show the raw data

# Option 1: Seeking to the known offset, read the length, then the string
with open('sample.bin','rb') as f:
    f.seek(10)
    length = int.from_bytes(f.read(2),'big')
    result = f.read(length).decode()
    print(result)

# Option 2: read the fixed portion as a structure.
with open('sample.bin','rb') as f:
    # read 10 bytes and a big endian 16-bit value
    *other,header = struct.unpack('>10bH',f.read(12))
    result = f.read(length).decode()
    print(result)

输出:

b'\xa3\x1e\x07S8\xb9LA\xf0_\x003\xe6\x88\x91\xe4\xb8\x8d\xe5\x96\x9c\xe6\xac\xa2\xe4\xbd\xa0\xe5\xa5\xb3\xe6\x9c\x8b\xe5\x8f\x8b\xe3\x80\x82\xe4\xbd\xa0\xe9\x9c\x80\xe8\xa6\x81\xe4\xb8\x80\xe4\xb8\xaa\xe6\x96\xb0\xe7\x9a\x84\xe3\x80\x82ta\xacg\x9c\x82\x85\x95\xf9\x8c'
我不喜欢你女朋友。你需要一个新的。
我不喜欢你女朋友。你需要一个新的。

如果您确实需要从文件中的特定字节偏移读取 UTF-8 字符,您可以在查找后将二进制流包装在 UTF-8 reader 中:

with open('sample.bin','rb') as f:
    f.seek(12)
    c = codecs.getreader('utf8')(f)
    print(c.read(1))

输出: