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

Set*_*ton 8 python unicode locale python-3.x

使用locale具有unicode输入的库时,我遇到了奇怪的行为.以下是最低工作示例:

>>> 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]
Run Code Online (Sandbox Code Playgroud)

我在Python 3.3,3.4和3.5上看过这个.我在Python 2.7上没有出错.

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

我正在运行Mac OS X,这种行为可能与http://bugs.python.org/issue23195有关......但我认为这个bug只会表现为不正确的结果,而不是引发异常.我无法在我的SLES 11机器上复制,其他人确认它们无法在Ubuntu,Centos或Windows上复制.在评论中听到其他操作系统可能是有益的.

有人可以解释一下这里可能发生的事情吗?

mne*_*cia 8

在Python 3.x中,该函数在locale.strxfrm(s)内部使用POSIX C函数wcsxfrm(),该函数基于当前的LC_COLLATE设置.POSIX标准以这种方式定义转换:

变换应该是这样的:如果wcscmp()应用于两个变换的宽字符串,它将返回一个大于,等于或小于0的值,对应于wcscoll()应用于相同的两个原始宽字符串的结果.

该定义可以以多种方式实现,甚至不要求结果字符串是可读的.

我已经创建了一个小的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");
}
Run Code Online (Sandbox Code Playgroud)

它在转换之前和之后打印字符串.

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

in : 0x10fefd
out: 0x1 0x1 0x1 0x1 0x552
Run Code Online (Sandbox Code Playgroud)

在OSX(10.11.1)上运行它时,结果是:

in : 0x10fefd
out: 0x103 0x1 0x110000
Run Code Online (Sandbox Code Playgroud)

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

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

更新:

进一步研究,我发现OSX上en_US.UTF-8的LC_COLLATE定义是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
Run Code Online (Sandbox Code Playgroud)

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

order \
    \x00;...;\xff
Run Code Online (Sandbox Code Playgroud)

第二次更新:

wcsxfrm()在OSX上进一步测试了这个功能.使用la_LN.US-ASCII排序规则,给定一个宽字符序列C1..Cn作为输入,输出是一个具有以下形式的字符串:

W1..Wn \x01 U1..Un
Run Code Online (Sandbox Code Playgroud)

哪里

Wx = 0x103 if Cx > 0xFF else Cx+0x3
Ux = Cx+0x103 if Cx > 0xFF else Cx+0x3
Run Code Online (Sandbox Code Playgroud)

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

我已经检查过,每个UTF-8语言环境都在OSX上使用这个分类,所以我倾向于说苹果系统上UTF-8的整理支持被打破了.得到的排序几乎与通过正常字节比较获得的顺序相同,并且具有获得非法Unicode字符的能力.