调用时 Unicode 字符不在范围内 locale.strxfrm

Unicode character not in range when calling locale.strxfrm

我在使用带有 unicode 输入的 locale 库时遇到了一个奇怪的行为。下面是一个最小的工作示例:

>>> x = '\U0010fefd'
>>> ord(x)
1113853
>>> ord('\U0010fefd') == 0X10fefd
True
>>> ord(x) <= 0X10ffff
True
>>> import locale
>>> locale.strxfrm(x)
'\U0010fefd'
>>> locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
'en_US.UTF-8'
>>> locale.strxfrm(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: character U+110000 is not in range [U+0000; U+10ffff]

我在 Python 3.3、3.4 和 3.5 上看到过这个。我在 Python 2.7.

上没有收到错误

据我所知,我的 unicode 输入在适当的 unicode 范围内,因此似乎在使用 'en_US.UTF-8' 时 strxfrm 内部的某种东西将输入移出范围.

我是 运行 Mac OS X,此行为可能与 http://bugs.python.org/issue23195 有关...但我的印象是此错误只会出现作为不正确的结果,而不是引发的异常。我无法在我的 SLES 11 机器上复制,其他人确认他们无法在 Ubuntu、Centos 或 Windows 上复制。在评论中听到其他 OS 可能会有所启发。

谁能解释一下幕后情况?

在Python3.x中,函数locale.strxfrm(s) internally uses the POSIX C function wcsxfrm()是基于当前的LC_COLLATE设置。 POSIX 标准以这种方式定义转换:

The transformation shall be such that if wcscmp() is applied to two transformed wide strings, it shall return a value greater than, equal to, or less than 0, corresponding to the result of wcscoll() applied to the same two original wide-character strings.

这个定义可以通过多种方式实现,甚至不需要生成的字符串是可读的。

我创建了一个小的 C 代码示例来演示它是如何工作的:

#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main() {
  wchar_t buf[10];
  wchar_t *in = L"\x10fefd";
  int i;

  setlocale(LC_COLLATE, "en_US.UTF-8");

  printf("in : ");
  for(i=0;i<10 && in[i];i++)
    printf(" 0x%x", in[i]);
  printf("\n");

  i = wcsxfrm(buf, in, 10);

  printf("out: ");
  for(i=0;i<10 && buf[i];i++)
    printf(" 0x%x", buf[i]);
  printf("\n");
}

打印转换前后的字符串。

运行 它在 Linux (Debian Jessie) 这是结果:

in : 0x10fefd
out: 0x1 0x1 0x1 0x1 0x552

而 运行 它在 OSX (10.11.1) 结果是:

in : 0x10fefd
out: 0x103 0x1 0x110000

可以看到 OSX 上 wcsxfrm() 的输出包含 Python 字符串中不允许的字符 U+110000,因此这是错误的来源.

在 Python 2.7 上不会引发错误,因为其 locale.strxfrm() 实现基于 strxfrm() C 函数。

更新:

进一步调查,我发现 OSX 上 en_US.UTF-8 的 LC_COLLATE 定义是 link 到 la_LN.US-ASCII 定义。

$ ls -l /usr/share/locale/en_US.UTF-8/LC_COLLATE
lrwxr-xr-x 1 root wheel 28 Oct  1 14:24 /usr/share/locale/en_US.UTF-8/LC_COLLATE -> ../la_LN.US-ASCII/LC_COLLATE

我在 Apple 的 sources 中找到了实际定义。文件 la_LN.US-ASCII.src 的内容如下:

order \
    \x00;...;\xff

第二次更新:

我在 OSX 上进一步测试了 wcsxfrm() 函数。使用 la_LN.US-ASCII 整理,给定宽字符序列 C1..Cn 作为输入,输出是具有以下形式的字符串:

W1..Wn \x01 U1..Un

哪里

Wx = 0x103 if Cx > 0xFF else Cx+0x3
Ux = Cx+0x103 if Cx > 0xFF else Cx+0x3

使用这个算法\x10fefd变成0x103 0x1 0x110000

我已经检查过,每个 UTF-8 语言环境都在 OSX 上使用此整理,所以我倾向于说 Apple 系统上对 UTF-8 的整理支持已损坏。生成的排序与通过正常字节比较获得的排序几乎相同,并且能够获取非法 Unicode 字符。