将字符编码映射到每个字符的最大字节数

Mapping of character encodings to maximum bytes per character

我正在寻找一个 table 将给定的字符编码映射到每个字符的(最大,在可变长度编码的情况下)字节数。对于固定宽度的编码,这很容易,但我不知道,在一些更深奥的编码的情况下,宽度是多少。对于 UTF-8 等,最好根据字符串中的最高代码点 确定每个字符的最大字节数 ,但这并不那么紧迫。

对于一些背景知识(如果你不熟悉 Numpy,你可以忽略它,我正在研究一个 ndarray 子类的原型,它可以以一定的透明度表示编码字节数组(包括纯 ASCII)作为 unicode 字符串数组,而不是一次将整个数组实际转换为 UCS4。这个想法是底层 dtype 仍然是 S<N> dtype,其中 <N> 是数组中每个字符串的(最大)字节数。但是项目查找和字符串方法使用正确的编码即时解码字符串。可以看到一个非常粗略的原型 here,尽管最终这部分可能会在 C 中实现。对我的用例来说,最重要的是有效使用内存,而字符串的重复解码和重新编码是 acceptable 开销。

无论如何,因为底层 dtype 以字节为单位,所以它不会告诉 用户 任何关于可以写入给定编码文本数组的字符串长度的有用信息。因此,如果没有别的,拥有这样一个任意编码的映射对于改进用户界面将非常有用。

注意: 我在这里找到了 特定于 到 Java 的基本相同问题的答案:How can I programatically determine the maximum size in bytes of a character in a specific charset? 但是,我无法在 Python 中找到任何等效项,也无法找到一个有用的信息数据库来实现我自己的。

蛮力法。遍历所有可能的 Unicode 字符并跟踪使用的最大字节数。

def max_bytes_per_char(encoding):
    max_bytes = 0
    for codepoint in range(0x110000):
        try:
            encoded = chr(codepoint).encode(encoding)
            max_bytes = max(max_bytes, len(encoded))
        except UnicodeError:
            pass
    return max_bytes


>>> max_bytes_per_char('UTF-8')
4

虽然我接受了@dan04 的答案,但我也在这里添加了我自己的答案,该答案受到@dan04 的启发,但更进一步,它给出了给定编码支持的所有字符的编码宽度,并且编码到该宽度的字符范围(其中宽度 0 表示它不受支持):

从集合导入 defaultdict

def encoding_ranges(encoding):
    codepoint_ranges = defaultdict(list)
    cur_nbytes = None
    start = 0
    for codepoint in range(0x110000):
        try:
            encoded = chr(codepoint).encode(encoding)
            nbytes = len(encoded)
        except UnicodeError:
            nbytes = 0

        if nbytes != cur_nbytes and cur_nbytes is not None:
            if codepoint - start > 2:
                codepoint_ranges[cur_nbytes].append((start, codepoint))
            else:
                codepoint_ranges[cur_nbytes].extend(range(start, codepoint))

            start = codepoint

        cur_nbytes = nbytes

    codepoint_ranges[cur_nbytes].append((start, codepoint + 1))
    return codepoint_ranges

例如:

>>> encoding_ranges('ascii')
defaultdict(<class 'list'>, {0: [(128, 1114112)], 1: [(0, 128)]})
>>> encoding_ranges('utf8')
defaultdict(<class 'list'>, {0: [(55296, 57344)], 1: [(0, 128)], 2: [(128, 2048)], 3: [(2048, 55296), (57344, 65536)], 4: [(65536, 1114112)]})
>>> encoding_ranges('shift_jis')

对于 2 个或更少字符的范围,它只记录代码点本身而不是范围,这对于更笨拙的编码更有用,例如 shift_jis。