Uruchamiam ten fragment dwukrotnie, w terminalu Ubuntu (kodowanie ustawione na utf-8), raz z, ./test.py
a potem z ./test.py >out.txt
:
uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni
Bez przekierowania drukuje śmieci. Z przekierowaniem otrzymuję UnicodeDecodeError. Czy ktoś może wyjaśnić, dlaczego otrzymuję błąd dopiero w drugim przypadku, czy jeszcze lepiej udzielić szczegółowego wyjaśnienia, co dzieje się za kurtyną w obu przypadkach?
Odpowiedzi:
Cały klucz do takich problemów z kodowaniem polega na zrozumieniu, że w zasadzie istnieją dwa różne pojęcia „ciągu znaków” : (1) ciąg znaków i (2) ciąg / tablica bajtów. To rozróżnienie było przez długi czas ignorowane z powodu historycznej wszechobecności kodowań zawierających nie więcej niż 256 znaków (ASCII, Latin-1, Windows-1252, Mac OS Roman,…): te kodowania odwzorowują zestaw typowych znaków na liczby od 0 do 255 (tj. bajty); stosunkowo ograniczona wymiana plików przed pojawieniem się Internetu sprawiła, że sytuacja niezgodnego kodowania była tolerowana, ponieważ większość programów mogła ignorować fakt, że było wiele kodowań, o ile tworzyły tekst, który pozostawał w tym samym systemie operacyjnym: takie programy po prostu traktuj tekst jako bajty (zgodnie z kodowaniem używanym przez system operacyjny). Prawidłowy, nowoczesny pogląd właściwie rozdziela te dwie koncepcje strun, w oparciu o następujące dwa punkty:
Postacie są w większości niezwiązane z komputerami : można je narysować na tablicy kredowej itp., Jak na przykład بايثون, 中 蟒 i 🐍. „Znaki” dla maszyn obejmują również „instrukcje rysowania”, takie jak na przykład spacje, powrót karetki, instrukcje ustawiania kierunku pisania (w przypadku języka arabskiego itp.), Akcenty itp. Standard Unicode zawiera bardzo dużą listę znaków ; obejmuje większość znanych postaci.
Z drugiej strony komputery muszą w jakiś sposób reprezentować abstrakcyjne znaki: w tym celu używają tablic bajtów (włącznie z liczbami od 0 do 255), ponieważ ich pamięć jest podzielona na fragmenty bajtów. Niezbędny proces, który konwertuje znaki na bajty, nazywa się kodowaniem . Dlatego komputer wymaga kodowania, aby przedstawić znaki. Każdy tekst znajdujący się na Twoim komputerze jest kodowany (do momentu wyświetlenia), czy jest przesyłany do terminala (który oczekuje znaków zakodowanych w określony sposób), czy też zapisywany w pliku. Aby wyświetlić lub właściwie „zrozumieć” (np. Przez interpreter Pythona), strumienie bajtów są dekodowane na znaki. Kilka kodowań(UTF-8, UTF-16,…) są zdefiniowane przez Unicode dla jego listy znaków (Unicode definiuje więc zarówno listę znaków, jak i kodowanie dla tych znaków - wciąż są miejsca, w których wyrażenie „kodowanie Unicode” jest sposób odwoływania się do wszechobecnego UTF-8, ale jest to niepoprawna terminologia, ponieważ Unicode zapewnia wiele kodowań).
Podsumowując, komputery muszą wewnętrznie reprezentować znaki za pomocą bajtów i robią to za pomocą dwóch operacji:
Niektóre kodowania nie mogą zakodować wszystkich znaków (np. ASCII), podczas gdy (niektóre) kodowania Unicode pozwalają na zakodowanie wszystkich znaków Unicode. Kodowanie również niekoniecznie jest unikalne , ponieważ niektóre znaki mogą być przedstawiane bezpośrednio lub jako kombinacja (np. Znaku podstawowego i akcentów).
Zauważ, że koncepcja nowej linii dodaje warstwę komplikacji , ponieważ może być reprezentowana przez różne (sterujące) znaki, które zależą od systemu operacyjnego (jest to powód uniwersalnego trybu odczytu plików nowej linii w Pythonie ).
To, co powyżej nazwałem „znakiem”, jest tym, co Unicode nazywa „ znakiem postrzeganym przez użytkownika ”. Pojedynczy znak postrzegany przez użytkownika może być czasami reprezentowany w Unicode przez połączenie części znaku (znak bazowy, akcenty,…) znalezionych w różnych indeksach na liście Unicode, które są nazywane „ punktami kodowymi” - te punkty kodowe można łączyć ze sobą w „grapheme cluster”. W ten sposób Unicode prowadzi do trzeciej koncepcji łańcucha, złożonej z sekwencji punktów kodowych Unicode, która znajduje się między ciągami bajtów i znaków i jest bliższa temu drugiemu. Nazwę je „ ciągami Unicode ” (jak w Pythonie 2).
Podczas gdy Python może drukować ciągi znaków (postrzeganych przez użytkownika), łańcuchy niebajtowe w Pythonie są zasadniczo sekwencjami punktów kodowych Unicode , a nie znaków postrzeganych przez użytkownika. Wartości punktów kodowych są używane w składni napisów Python
\u
i\U
Unicode. Nie należy ich mylić z kodowaniem znaku (i nie muszą mieć z tym żadnego związku: punkty kodowe Unicode można kodować na różne sposoby).Ma to ważną konsekwencję: długość łańcucha w Pythonie (Unicode) to liczba punktów kodowych, która nie zawsze jest liczbą znaków postrzeganych przez użytkownika : w ten sposób
s = "\u1100\u1161\u11a8"; print(s, "len", len(s))
(Python 3) daje각 len 3
pomimos
posiadania jednego postrzeganego przez użytkownika (koreański) znak (ponieważ jest reprezentowany przez 3 punkty kodowe - nawet jeśli nie musi, jakprint("\uac01")
widać). Jednak w wielu praktycznych okolicznościach długość łańcucha to liczba znaków postrzeganych przez użytkownika, ponieważ wiele znaków jest zwykle przechowywanych przez Python jako pojedynczy punkt kodowy Unicode.W Pythonie 2 ciągi znaków Unicode nazywane są… „ciągami znaków Unicode” (
unicode
typ, forma literałuu"…"
), podczas gdy tablice bajtów są „łańcuchami” (str
typ, gdzie tablicę bajtów można na przykład skonstruować za pomocą literałów łańcuchowych"…"
). W Pythonie 3 ciągi znaków Unicode nazywane są po prostu „ciągami” (str
typ, forma literału"…"
), podczas gdy tablice bajtów to „bajty” (bytes
typ, forma literałub"…"
). W konsekwencji coś takiego"🐍"[0]
daje inny wynik w Pythonie 2 ('\xf0'
bajt) i Pythonie 3 ("🐍"
pierwszy i jedyny znak).Mając kilka kluczowych punktów, powinieneś być w stanie zrozumieć większość pytań związanych z kodowaniem!
Normalnie, kiedy drukujesz
u"…"
na terminalu , nie powinieneś dostać śmieci: Python zna kodowanie twojego terminala. W rzeczywistości możesz sprawdzić, jakiego kodowania oczekuje terminal:Jeśli twoje znaki wejściowe mogą być zakodowane za pomocą kodowania terminala, Python zrobi to i wyśle odpowiednie bajty do twojego terminala bez narzekania. Terminal zrobi wszystko, co w jego mocy, aby wyświetlić znaki po zdekodowaniu bajtów wejściowych (w najgorszym przypadku czcionka terminala nie zawiera niektórych znaków i zamiast tego wypisze jakieś puste miejsce).
Jeśli znaki wejściowe nie mogą być zakodowane za pomocą kodowania terminala, oznacza to, że terminal nie jest skonfigurowany do wyświetlania tych znaków. Python będzie narzekał (w Pythonie z a,
UnicodeEncodeError
ponieważ ciąg znaków nie może być zakodowany w sposób, który pasuje do twojego terminala). Jedynym możliwym rozwiązaniem jest użycie terminala, który może wyświetlać znaki (albo przez skonfigurowanie terminala tak, aby akceptował kodowanie, które może reprezentować twoje znaki, lub używając innego programu terminala). Jest to ważne, gdy rozpowszechniasz programy, które mogą być używane w różnych środowiskach: komunikaty, które drukujesz, powinny być reprezentowalne na terminalu użytkownika. Dlatego czasami najlepiej jest trzymać się łańcuchów zawierających tylko znaki ASCII.Jednakże, gdy przekierowujesz lub potokujesz wyjście swojego programu, generalnie nie jest możliwe ustalenie, jakie jest kodowanie wejściowe programu odbierającego, a powyższy kod zwraca pewne domyślne kodowanie: Brak (Python 2.7) lub UTF-8 ( Python 3):
W razie potrzeby kodowanie stdin, stdout i stderr można jednak ustawić za pomocą
PYTHONIOENCODING
zmiennej środowiskowej:Jeśli drukowanie na terminalu nie przyniesie oczekiwanych rezultatów, możesz sprawdzić, czy kodowanie UTF-8, które wprowadziłeś ręcznie, jest poprawne; na przykład, jeśli się nie mylę , pierwszego znaku (
\u001A
) nie można wydrukować .Pod adresem http://wiki.python.org/moin/PrintFails można znaleźć rozwiązanie podobne do poniższego dla Pythona 2.x:
W przypadku Pythona 3 możesz sprawdzić jedno z pytań zadanych wcześniej w StackOverflow.
źródło
Python zawsze koduje łańcuchy Unicode podczas zapisywania do terminala, pliku, potoku itp. Podczas zapisywania do terminala Python może zwykle określić kodowanie terminala i używać go poprawnie. Podczas zapisywania do pliku lub potoku Python domyślnie stosuje kodowanie „ascii”, chyba że wyraźnie zaznaczono inaczej. Pythonowi można powiedzieć, co ma robić, przesyłając dane wyjściowe przez
PYTHONIOENCODING
zmienną środowiskową. Powłoka może ustawić tę zmienną przed przekierowaniem wyjścia Pythona do pliku lub potoku, aby znane było prawidłowe kodowanie.W twoim przypadku wydrukowałeś 4 nietypowe znaki, których twój terminal nie obsługiwał w swojej czcionce. Oto kilka przykładów, które pomogą wyjaśnić zachowanie, ze znakami, które są faktycznie obsługiwane przez mój terminal (który używa cp437, a nie UTF-8).
Przykład 1
Zwróć uwagę, że
#coding
komentarz wskazuje kodowanie, w jakim zapisywany jest plik źródłowy . Wybrałem utf8, więc mogłem obsługiwać znaki w źródle, których mój terminal nie mógł. Kodowanie zostało przekierowane na standardowe wyjście błędów, więc można to zobaczyć po przekierowaniu do pliku.Wyjście (uruchamiane bezpośrednio z terminala)
Python poprawnie określił kodowanie terminala.
Dane wyjściowe (przekierowane do pliku)
Python nie mógł określić kodowania (brak), więc użył domyślnego „ascii”. ASCII obsługuje tylko konwersję pierwszych 128 znaków Unicode.
Wyjście (przekierowane do pliku, PYTHONIOENCODING = cp437)
a mój plik wyjściowy był poprawny:
Przykład 2
Teraz wrzucę znak w źródle, który nie jest obsługiwany przez mój terminal:
Wyjście (uruchamiane bezpośrednio z terminala)
Mój terminal nie zrozumiał tego ostatniego chińskiego znaku.
Wyjście (uruchom bezpośrednio, PYTHONIOENCODING = 437: wymień)
Procedury obsługi błędów można określić za pomocą kodowania. W tym przypadku nieznane znaki zostały zastąpione
?
.ignore
ixmlcharrefreplace
kilka innych opcji. W przypadku korzystania z UTF8 (który obsługuje kodowanie wszystkich znaków Unicode), nigdy nie zostaną dokonane zamiany, ale czcionka używana do wyświetlania znaków musi nadal je obsługiwać.źródło
PYTHONIOENCODING
. Robiprint string.encode("UTF-8")
jak sugeruje @Ismail pracował dla mnie.chcp
strona kodowa ich nie obsługuje. Aby tego uniknąćUnicodeEncodeError: 'charmap'
, możesz zainstalowaćwin-unicode-console
pakiet.PYTHONIOENCODING=utf-8
rozwiązuje problem.Zakoduj go podczas drukowania
Dzieje się tak, ponieważ gdy uruchamiasz skrypt ręcznie, python koduje go przed wyprowadzeniem go do terminala, a kiedy potokujesz go, python nie koduje go sam, więc musisz kodować ręcznie podczas wykonywania operacji we / wy.
źródło
win-unicode-console
(Windows) lub zaakceptuj parametr wiersza polecenia (jeśli musisz).