字符串长度不等于字节数:用Python检查UTF-8文本大小
在给接口字段做大小检查、记录日志或估算文本体积时,常见的问题是把“字符串长度”直接当作“字节数”。同样是一段可读文本,采用不同编码、是否包含换行、是否先转成JSON,都会影响最终字节序列。本文只解决一个小问题:已知Python字符串,如何计算它按UTF-8编码后的长度。
先明确统计对象
Python 3的str表示Unicode文本,len(text)统计Unicode码点的数量;text.encode("utf-8")返回bytes,再对它调用len,才是这次UTF-8编码结果的字节数。码点数也不一定等于人眼看到的字形数量,因此不要把这两个值都笼统叫作“字数”。
例如,ASCII文本abc有3个码点、3字节;“云”有1个码点、3字节;😀有1个码点、4字节。不能用“中文总是几个字符”之类的粗略倍数替代实际编码。
一个可以直接运行的检查
samples = [
("", 0),
("abc", 3),
("云", 3),
("😀", 4),
("a\nb", 3),
("云服务器", 12),
]
for text, expected in samples:
data = text.encode("utf-8")
assert len(data) == expected
print(ascii(text), "code_points=", len(text), "utf8_bytes=", len(data))
以上六组输入已在本地运行检查。UTF-8字节数依次为0、3、3、4、3、12。使用ascii(text)打印样例,是为了让换行、中文和emoji都能以转义形式显示,减少终端自身输出编码带来的干扰;它只影响展示,不改变被测text。
这段代码没有统计什么
这里算的是当前字符串编码后的裸字节数,不包含文件的BOM、HTTP请求头、TLS开销,也不代表压缩后的网络流量。如果实际要发送JSON,应先按真实序列化方式生成请求体,再统计最终字节;不要用某个字段的长度代替整个请求的长度。
换行也属于内容:"a\nb"中的换行是一个LF,编码后合计3字节;如果原文件使用CRLF,结果会不同。读取文本时可能发生换行转换,因此需要确认原文件真实大小时,应检查文件本身的字节,而不是把读出的文本重新编码后假定两者相等。
外观看起来相同,也可能不同
é可以由单一码点表示,也可以由e和组合重音两个码点表示;它们按UTF-8编码分别是2字节和3字节。本例按原输入计算,不自动做Unicode规范化。业务是否要规范化,应由字段协议决定,不能为了减少大小而擅自改写用户内容。
实际使用时,先确定协议使用的编码和限制单位,再对最终待写入或待发送的内容进行检查。本文没有调用云资源或发送网络请求,仅演示本地文本编码。
参考:Python官方文档《Unicode HOWTO》,https://docs.python.org/3/howto/unicode.html 。
- 点赞
- 收藏
- 关注作者
评论(0)