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

时间:2015-06-16 14:10:20

标签: python numpy character-encoding

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

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

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

注意:我在这里找到了与特定基本相同的问题的答案:How can I programatically determine the maximum size in bytes of a character in a specific charset?但是,我找不到Python中的任何等价物,也不是我可能实现自己的有用信息数据库。

2 个答案:

答案 0 :(得分:1)

蛮力方法。迭代所有可能的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

答案 1 :(得分:0)

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

from collections import 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这样更笨拙的编码更有用。