2015-11-01 13 views
8

Występuje dziwne zachowanie podczas korzystania z biblioteki locale z wejściem Unicode. Poniżej znajduje się minimalny przykład roboczy:Znak Unicode spoza zakresu podczas wywoływania locale.strxfrm

>>> 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] 

Widziałem to na Pythonie 3.3, 3.4 i 3.5. Nie dostaję błędu w Pythonie 2.7.

O ile widzę, moje wejście unicode jest w odpowiednim zakresie Unicode, więc wydaje się, że jakoś coś wewnętrznego do strxfrm przy użyciu "en_US.UTF-8" przesuwa wejście poza zasięg.

Używam Mac OS X, a to zachowanie może być związane z http://bugs.python.org/issue23195 ... ale byłem pod wrażeniem, że ten błąd będzie manifestował się jako nieprawidłowe wyniki, a nie podniesiony wyjątek. Nie mogę zreplikować na moim komputerze SLES 11, a inni potwierdzają, że nie mogą replikować w systemie Ubuntu, Centos lub Windows. Może być pouczające słuchać o innych systemach operacyjnych w komentarzach.

Czy ktoś może wyjaśnić, co może się tu dziać pod maską?

+0

Nie mogę tego odtworzyć na Ubuntu. 'locale.strxfrm (x)' zwraca ''\ x01 \ x01 \ x01 \ x01 Ւ'' w lokalizacji' en_US.UTF-8'. – jfs

+1

można użyć ['icu.Collator.createInstance (icu.Locale ('en_US')). GetSortKey' zamiast] (http://stackoverflow.com/a/32178778/4279) – jfs

+0

@JFSebastian Tak, użyłem PyICU i potwierdź, że nie ma problemu. Byłem bardziej zaniepokojony tym zachowaniem w module "locale" stdlib i jeśli był to jakiś błąd użytkownika (tzn. Zrobiłem coś złego) lub jeśli dzieje się coś bardziej niecnego. – SethMMorton

Odpowiedz

7

W języku Python 3.x funkcja locale.strxfrm(s) wewnętrznie używa funkcji POSIX C wcsxfrm(), która jest oparta na bieżącym ustawieniu LC_COLLATE. Standard POSIX zdefiniować transformację w ten sposób:

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.

Definicja ta może być realizowana na wiele sposobów i nie wymaga nawet, że otrzymany ciąg jest czytelny.

Utworzyłem mały przykład kodu C, aby pokazać, jak to działa:

#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"); 
} 

wypisuje ciąg przed i po transformacji.

Running go na Linux (Debian Jessie) jest to wynik:

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

podczas uruchamiania go na OSX (10.11.1) wynik jest:

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

Można zobaczyć, że wyjście wcsxfrm() na OSX zawiera znak U + 110000, który nie jest dozwolony w łańcuchu Pythona, więc jest to źródło błędu.

W Pythonie 2.7 błąd nie został zgłoszony, ponieważ jego implementacja locale.strxfrm() opiera się na funkcji strxfrm() C.

UPDATE:

Badając dalej, widzę, że definicja LC_COLLATE dla en_US.UTF-8 na OSX jest link do definicji 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 

Znalazłem rzeczywistą definicję od sources od Apple.Zawartość pliku la_LN.US-ASCII.src jest następujący:

order \ 
    \x00;...;\xff 

2. Aktualizacja:

mam dalej testowane funkcję wcsxfrm() na OSX. Korzystanie z sortowania la_LN.US-ASCII, daną sekwencję znak szeroki C1..Cn jako wejście, wyjście jest ciągiem z tego formularza:

W1..Wn \x01 U1..Un 

gdzie

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

Stosując ten algorytm \x10fefd stać 0x103 0x1 0x110000

Sprawdziłem i wszystkie ustawienia regionalne UTF-8 używają tego zestawienia w OSX, więc jestem skłonny powiedzieć, że obsługa zestawiania UTF-8 w systemach Apple jest zepsuta. Wynikowa kolejność jest prawie taka sama jak w przypadku porównania z normalnym bajtem, z premią w postaci możliwości uzyskania nielegalnych znaków Unicode.

+0

Huh. Wygląda na to, że niewiele mogę zrobić, aby zapobiec "ValueError", ponieważ pochodzi on z podstawowej biblioteki C, poza kontrolą Pythona. – SethMMorton

+0

Zastanawiam się, czy byłby to błąd. Zakładając, że '0x110000' jest poprawną wartością zwracaną przez' wcsxfrm() ', to Python powinien wewnętrznie być w stanie sobie z tym poradzić, prawda? Jeśli jednak "0x110000" jest niepoprawne, to przypuszczam, że to, co robi Python, byłoby "poprawne". – SethMMorton

+0

Wygląda na to, że pojawił się 4 lata temu: https://mail.python.org/pipermail/python-dev/2011-December/114759.html i http://bugs.python.org/issue13441. Z moich oczu nie wygląda na to, że znaleźli rozwiązanie dla błędów dla wartości> = 0x110000, ale konsensus był taki, że zdecydowanie ich nie chcą. – SethMMorton