Python 3- 检查缓冲的字节是否形成有效的字符

Python 3- check if buffered out bytes form a valid char

我正在将一些代码从 python 2.7 移植到 3.4.2,我对字节与字符串的复杂性感到震惊。

我看了this狼的回答第3点

Exactly n bytes may cause a break between logical multi-byte characters (such as \r\n in binary mode and, I think, a multi-byte character in Unicode) or some underlying data structure not known to you;

所以,当我缓冲读取一个文件(比如说 - 每次 1 个字节)并且第一个字符恰好是一个 6-byte unicode 我如何计算出要读取多少字节?因为如果我不读到完整的字符,它将被跳过处理;因为下一次 read(x) 将读取相对于它的最后位置的 x 个字节(即在它之间的一半相当于 char 的字节)

我尝试了以下方法:

import sys, os

def getBlocks(inputFile, chunk_size=1024):
    while True:
        try:
            data=inputFile.read(chunk_size)
            if data:
                yield data
            else:
                break
        except IOError as strerror:
            print(strerror)
            break

def isValid(someletter):
    try:
        someletter.decode('utf-8', 'strict')
        return True
    except UnicodeDecodeError:
        return False

def main(src):
    aLetter = bytearray()
    with open(src, 'rb') as f:
        for aBlock in getBlocks(f, 1):
            aLetter.extend(aBlock)
            if isValid(aLetter):
                # print("char is now a valid one") # just for acknowledgement
                # do more
            else:
                aLetter.extend( getBlocks(f, 1) )

问题:

  1. 如果我尝试,我是否注定失败fileHandle.seek(-ve_value_here, 1)
  2. Python 一定有内置的东西来处理这个,它是什么?
  3. 我怎样才能真正测试程序是否达到确保读取完整字符的目的(现在我只有简单的英文文件)
  4. 如何确定最佳 chunk_size 以使程序更快。我的意思是读取 1024 个字节,其中前 1023 个字节是 1-byte-representable-char & 最后一个是 6-byte 让我只能选择每次读取 1 个字节

注意:我不喜欢缓冲阅读,因为我事先不知道输入文件大小的范围

#2 的答案将解决您的大部分问题。使用 IncrementalDecoder via codecs.getincrementaldecoder。解码器保持状态,只输出完全解码的序列:

#!python3
import codecs
import sys
byte_string = '\u5000\u5001\u5002'.encode('utf8')

# Get the UTF-8 incremental decoder.
decoder_factory = codecs.getincrementaldecoder('utf8')
decoder_instance = decoder_factory()

# Simple example, read two bytes at a time from the byte string.
result = ''
for i in range(0,len(byte_string),2):
    chunk = byte_string[i:i+2]
    result += decoder_instance.decode(chunk)
    print('chunk={} state={} result={}'.format(chunk,decoder_instance.getstate(),ascii(result)))
result += decoder_instance.decode(b'',final=True)
print(ascii(result))

输出:

chunk=b'\xe5\x80' state=(b'\xe5\x80', 0) result=''
chunk=b'\x80\xe5' state=(b'\xe5', 0) result='\u5000'
chunk=b'\x80\x81' state=(b'', 0) result='\u5000\u5001'
chunk=b'\xe5\x80' state=(b'\xe5\x80', 0) result='\u5000\u5001'
chunk=b'\x82' state=(b'', 0) result='\u5000\u5001\u5002'
'\u5000\u5001\u5002'

请注意,在处理完前两个字节后,内部解码器状态只是缓冲它们并且不向结果附加任何字符。接下来的两个完成了一个角色并留下一个处于内部状态。最后一次调用没有附加数据, final=True 只是刷新缓冲区。如果有不完整的字符挂起,它将引发异常。

现在您可以读取您想要的任何块大小的文件,将它们全部传递给解码器并确保您只有完整的代码点。

请注意,使用 Python 3,您可以只打开文件并声明编码。您阅读的 chunk 实际上将在内部使用 IncrementalDecoder 处理 Unicode 代码点:

input.csv(以无 BOM 的 UTF-8 格式保存)

我是美国人。
Normal text.

代码

with open('input.txt',encoding='utf8') as f:
    while True:
        data = f.read(2)   # reads 2 Unicode codepoints, not bytes.
        if not data: break
        print(ascii(data))

结果:

'\u6211\u662f'
'\u7f8e\u56fd'
'\u4eba\u3002'
'\nN'
'or'
'ma'
'l '
'te'
'xt'
'.'