unicode_escape
ogólnie nie działa
Okazuje się, że rozwiązanie string_escape
lub unicode_escape
ogólnie nie działa - w szczególności nie działa w obecności rzeczywistego Unicode.
Jeśli możesz być pewien, że każdy znak spoza ASCII zostanie usunięty (i pamiętaj, że wszystko poza pierwszymi 128 znakami nie jest ASCII), unicode_escape
zrobi to dobrze. Ale jeśli w twoim ciągu znajdują się już jakieś dosłowne znaki spoza ASCII, coś pójdzie nie tak.
unicode_escape
jest zasadniczo zaprojektowany do konwersji bajtów na tekst Unicode. Ale w wielu miejscach - na przykład w kodzie źródłowym Pythona - dane źródłowe są już tekstem Unicode.
Jedynym sposobem, w jaki może to działać poprawnie, jest zakodowanie tekstu w bajtach. UTF-8 to rozsądne kodowanie całego tekstu, więc to powinno działać, prawda?
Poniższe przykłady są w Pythonie 3, więc literały ciągów są czystsze, ale ten sam problem występuje z nieco innymi manifestacjami w obu Pythonie 2 i 3.
>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve test
Cóż, to źle.
Nowym zalecanym sposobem używania kodeków, które dekodują tekst na tekst, jest codecs.decode
bezpośrednie wywołanie . To pomaga?
>>> import codecs
>>> print(codecs.decode(s, 'unicode_escape'))
naïve test
Ani trochę. (Powyższe to również błąd UnicodeError w Pythonie 2.)
unicode_escape
Kodek, pomimo swojej nazwy, okazuje się założyć, że wszystkie bajty są non-ASCII w kodowaniu Latin-1 (ISO-8859-1). Więc musiałbyś to zrobić w ten sposób:
>>> print(s.encode('latin-1').decode('unicode_escape'))
naïve test
Ale to straszne. To ogranicza cię do 256 znaków Latin-1, tak jakby Unicode nigdy nie został wynaleziony!
>>> print('Ernő \\t Rubik'.encode('latin-1').decode('unicode_escape'))
UnicodeEncodeError: 'latin-1' codec can't encode character '\u0151'
in position 3: ordinal not in range(256)
Dodanie wyrażenia regularnego w celu rozwiązania problemu
(Co zaskakujące, nie mamy teraz dwóch problemów.)
To, co musimy zrobić, to zastosować unicode_escape
dekoder tylko do rzeczy, które na pewno są tekstem ASCII. W szczególności możemy upewnić się, że zastosujemy go tylko do prawidłowych sekwencji ucieczki Pythona, które na pewno będą tekstem ASCII.
Plan jest taki, że znajdziemy sekwencje specjalne przy użyciu wyrażenia regularnego i użyjemy funkcji jako argumentu, re.sub
aby zastąpić je wartością bez zmiany znaczenia.
import re
import codecs
ESCAPE_SEQUENCE_RE = re.compile(r'''
( \\U........ # 8-digit hex escapes
| \\u.... # 4-digit hex escapes
| \\x.. # 2-digit hex escapes
| \\[0-7]{1,3} # Octal escapes
| \\N\{[^}]+\} # Unicode characters by name
| \\[\\'"abfnrtv] # Single-character escapes
)''', re.UNICODE | re.VERBOSE)
def decode_escapes(s):
def decode_match(match):
return codecs.decode(match.group(0), 'unicode-escape')
return ESCAPE_SEQUENCE_RE.sub(decode_match, s)
I z tym:
>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő Rubik
'spam'+"eggs"+'''some'''+"""more"""
zostanie przetworzony ciąg zawierający ?