2011-08-03 16 views
8

Google App Engine używa Python 2.5.2, najwyraźniej z włączoną obsługą UCS4. Ale magazyn danych GAE używa wewnętrznie UTF-8. Więc jeśli przechowujesz u \ ud834 \ udd0c (długość 2) w magazynie danych, po pobraniu go otrzymasz "\ U0001d10c" (długość 1). Próbuję policzyć liczbę znaków unicode w ciągu znaków w sposób, który daje taki sam wynik przed i po przechowywaniu go. Próbuję więc znormalizować ciąg znaków (z u '\ ud834 \ udd0c' do '\ U0001d10c'), jak tylko go otrzymam, przed obliczeniem jego długości i umieszczeniem w magazynie danych. Wiem, że mogę po prostu zakodować go na UTF-8, a następnie ponownie dekodować, ale czy istnieje bardziej prosty/skuteczny sposób?Jak uzyskać wiarygodną liczbę znaków Unicode w Pythonie?

+4

Nie próbuj zapisywać 'u '\ ud834 \ udd0c''. Surogany nie są poprawnymi punktami kodowymi Unicode, więc nie powinieneś polegać na ich zachowaniu w łańcuchach lub długości działającej poprawnie. –

+1

Aby wyjaśnić: 'u'blah'' w Pythonie pojęciowo reprezentuje sekwencję punktów kodu Unicode. Nie powinieneś w nich umieszczać binarnej reprezentacji UTF-16. –

+0

Jeśli usunąłeś sentencję o kodowaniu i dekodowaniu ze swojego pytania i dodałeś ją jako odpowiedź, prawdopodobnie bym ją przegłosował, ponieważ uważam, że jest to najbardziej * poprawna * rzecz, aby uzyskać to, o co prosisz. – SingleNegationElimination

Odpowiedz

4

wiem, że mogę po prostu zakodować go na UTF-8, a następnie dekodować ponownie

Tak, to zwykle idiom, aby naprawić problem, gdy masz „UTF-16 surogatów w UCS-4 ciąg "input. Ale jak powiedział mechaniczny ślimak, ten sygnał wejściowy jest zniekształcony i powinieneś naprawiać wszystko, co jest produkowane w jego preferencjach.

Czy istnieje bardziej prosty/skuteczny sposób?

Cóż ... mógłby zrobić to ręcznie z regex, jak:

re.sub(
    u'([\uD800-\uDBFF])([\uDC00-\uDFFF])', 
    lambda m: unichr((ord(m.group(1))-0xD800<<10)+ord(m.group(2))-0xDC00+0x10000), 
    s 
) 
nie prościej

pewno ... Ja też mam wątpliwości co do tego, czy to jest rzeczywiście bardziej skuteczne!

+0

Dzięki. Moje dane wejściowe pochodzą ze strumienia yaml, który zawierał zastępcze jednostki kodowe, a ustalenie, że strumień nie zawiera surogatów, zdaje się uniemożliwiać ich wyświetlenie w unicode. Tak więc przyjrzę się naprawieniu strumienia. – Travis

+1

Jakie kodowanie jest YAML? Jeśli jest to kodowanie UTF-16, te znaki zastępcze * powinny * być konwertowane na pojedyncze znaki Unicode ... jeśli jednak jest to UTF-8, to producent popełnił błąd. (UTF-8-z-surogatami jest znany jako "CESU-8" i nie powinien być używany.) – bobince

+0

Jestem prawie pewien, że regex nie będzie bardziej wydajny. –

2

Niestety, zachowanie interpretera CPython w wersjach starszych niż 3.3 zależy od tego, czy jest zbudowany z "wąskim" lub "szerokim" wsparciem dla Unicode. Tak więc ten sam kod, na przykład wywołanie len, może mieć inny wynik w różnych wersjach standardowego interpretera. Zobacz przykłady this question.

Rozróżnienie między "wąskim" a "szerokim" polega na tym, że "wąscy" tłumacze wewnętrznie przechowują 16-bitowe jednostki kodowe (UCS-2), podczas gdy "szerokie" interpretery wewnętrznie przechowują 32-bitowe jednostki kodowe (UCS-4) . Kod punkty U + 10000 i powyżej (poza płaszczyzną podstawową-wielojęzyczna) mają len dwóch na „wąskie” tłumaczy, ponieważ potrzebne są dwa UCS-2 Kod szt je (za pomocą surogatów) reprezentują, i to, co len środki. W "szerokich" kompilacjach potrzebny jest tylko jeden kod UCS-4 jednostka dla kodu innego niż BMP punkt, więc dla tych kompilacji len jest jeden dla takich punktów kodowych.

Potwierdziłem, że poniżej obsługuje wszystkie ciągi znaków unicode niezależnie od tego, czy zawierają pary zastępcze, i działa w CPython 2.7 zarówno w wąskich, jak i szerokich kompilacjach. (Możliwe, że określenie ciągu znaków, takiego jak u'\ud83d\udc4d', w szerokim tłumaczeniu odzwierciedla twierdzące pragnienie przedstawienia kompletnego zastępczego kodu punkt w odróżnieniu od jednostki o kodach częściowych i dlatego nie jest automatycznie błędem do korekty, ale ja '. m ignorując że tutaj. jest to przypadek kant i zwykle nie jest pożądana przypadku użycia).

@invoke, stosowana poniżej sposób, aby uniknąć powtarzania obliczeń bez dodając do modułu __dict__.

invoke = lambda f: f() # trick taken from AJAX frameworks 

@invoke 
def codepoint_count(): 
    testlength = len(u'\U00010000') # pre-compute once 
    assert (testlength == 1) or (testlength == 2) 
    if testlength == 1: 
    def closure(data): # count function for "wide" interpreter 
     u'returns the number of Unicode code points in a unicode string' 
     return len(data.encode('UTF-16BE').decode('UTF-16BE')) 
    else: 
    def is_surrogate(c): 
     ordc = ord(c) 
     return (ordc >= 55296) and (ordc < 56320) 
    def closure(data): # count function for "narrow" interpreter 
     u'returns the number of Unicode code points in a unicode string' 
     return len(data) - len(filter(is_surrogate, data)) 
    return closure 

assert codepoint_count(u'hello \U0001f44d') == 7 
assert codepoint_count(u'hello \ud83d\udc4d') == 7 
Powiązane problemy