老项目中有时可以看到报错:unichr() arg not in range(0x10000) (narrow Python build)
Traceback (most recent call last):
File "/tmp/parser.py", line 107, in _inner
payload = HTML.unescape(payload)
File "/usr/local/lib/python2.7/HTMLParser.py", line 390, in unescape
return re.sub(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));", replaceEntities, s)
File "/usr/local/lib/python2.7/re.py", line 151, in sub
return _compile(pattern, flags).sub(repl, string, count)
File "/usr/local/lib/python2.7/HTMLParser.py", line 376, in replaceEntities
return unichr(c)
ValueError: unichr() arg not in range(0x10000) (narrow Python build)
经过一番调研之后,发现是和 Python 内部 Unicode 编码有关。
问题环境上(Python 2.7.15):
>>> import sys
>>> sys.maxunicode
65535
另一套环境(2.7.18):
>>> import sys
>>> sys.maxunicode
1114111
先理一理 Unicode 与 UCS 的关系
UCS 是 ISO 的国际标准(ISO/IEC 10646),全名 Universal Character Set,即通用字符集。
Unicode 是 IT 企业组成的 Unicode 联盟制定的标准。
两套标准目的都是为了统一各种字符编码,虽然独立发布,但是相互协同合作,保持了一致。
只是 Unicode 名头更响一些。
从 Unicode 2.0 开始,Unicode 采用了与 ISO 10646-1 相同的字库和字码;ISO 也承诺,ISO 10646 将不会替超出 U+10FFFF 的 UCS-4 编码赋值,以使得两者保持一致。
我的理解:
- ISO 和 Unicode 联盟共同维护了一套字符集,每个字对应到一个码位(Code Point),比如 “我” 字的码位为 6211
- ISO 这边叫做 UCS,Unicode 联盟这边叫做 Unicode
- 编码方案:
- UCS 对应 UCS-2 和 UCS-4 两套字符编码
- Unicode 对应 UTF 系列编码方案,常见的有 UTF-8、UTF-16、UTF-32 三种。
最流行的是 UTF-8,主要是对 ASCII 兼容的缘故(美国在 IT 技术领先地位的体现)。
- 不止字符集相同,在编码方面:
- UTF-16 完全覆盖 UCS-2,并采用变长的方式可以覆盖所有 Unicode 字符。
换句话说,UCS-2 就是 UTF-16 中两字节部分字符。 - UTF-32 和 UCS-4 都是固定长度编码,两者完全相同。
- UTF-16 完全覆盖 UCS-2,并采用变长的方式可以覆盖所有 Unicode 字符。
再说回来
65535 -> 0xFFFF
1114111 -> 0x10FFFF
- 65535 对应的就是两字节编码方案
- 1114111 对应的就是 4 字节编码方案
Unicode 规范使用的最大码位为 0x10FFFF,按他们的话来说就是一共使用 17 个平面(Plane),每个平面可容纳 2^16 个码位(Code Point)。
Python 2.x 编译时,可以通过参数 --enable-unicode=ucs2|ucs4
指定使用 UCS-2 还是 UCS-4,默认是 UCS-2。
Python3 不支持这个参数,直接使用的 UCS-4。
PS:下载 CentOS Python 2 源码确认了,CentOS 官方提供的 Python 编译时带上了 ucs4,不会有这个问题。
解决办法
$ ./configure --help | grep -i ucs
--enable-unicode[=ucs[24]]
重新编译 Python,./configure
参数加上 --enable-unicode=ucs4
就好了。