Myślę, że dzięki fragmentom z różnych odpowiedzi możemy zszyć wyjaśnienie.
Próbując wydrukować ciąg znaków Unicode, u '\ xe9', Python niejawnie próbuje zakodować ten ciąg przy użyciu schematu kodowania obecnie przechowywanego w sys.stdout.encoding. Python faktycznie pobiera to ustawienie ze środowiska, z którego został zainicjowany. Jeśli nie może znaleźć odpowiedniego kodowania w środowisku, tylko wtedy powraca do swojego domyślnego ASCII.
Na przykład używam powłoki bash, której kodowanie jest domyślnie ustawione na UTF-8. Jeśli uruchomię z niego Pythona, odbierze i użyje tego ustawienia:
$ python
>>> import sys
>>> print sys.stdout.encoding
UTF-8
Wyjdźmy na chwilę z powłoki Pythona i ustawmy środowisko basha z jakimś fałszywym kodowaniem:
$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.
Następnie ponownie uruchom powłokę Pythona i sprawdź, czy rzeczywiście powraca do domyślnego kodowania ascii.
$ python
>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968
Bingo!
Jeśli teraz spróbujesz wypisać jakiś znak Unicode poza ascii, powinieneś otrzymać ładny komunikat o błędzie
>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9'
in position 0: ordinal not in range(128)
Zamknijmy Pythona i odrzućmy powłokę bash.
Teraz będziemy obserwować, co się dzieje po tym, jak Python wyprowadza ciągi. W tym celu najpierw uruchomimy powłokę bash w terminalu graficznym (używam terminala Gnome) i ustawimy terminal na dekodowanie wyjścia za pomocą ISO-8859-1 aka latin-1 (terminale graficzne zwykle mają opcję Ustaw znak Kodowanie w jednym z rozwijanych menu). Zauważ, że nie zmienia to rzeczywistego kodowania środowiska powłoki , zmienia tylko sposób, w jaki terminal sam dekoduje dane wyjściowe, podobnie jak robi to przeglądarka internetowa. Możesz zatem zmienić kodowanie terminala, niezależnie od środowiska powłoki. Zacznijmy więc Pythona od powłoki i sprawdźmy, czy sys.stdout.encoding jest ustawione na kodowanie środowiska powłoki (dla mnie UTF-8):
$ python
>>> import sys
>>> print sys.stdout.encoding
UTF-8
>>> print '\xe9' # (1)
é
>>> print u'\xe9' # (2)
é
>>> print u'\xe9'.encode('latin-1') # (3)
é
>>>
(1) python wysyła ciąg binarny bez zmian, terminal odbiera go i próbuje dopasować jego wartość do mapy znaków latin-1. W Latin-1, 0xe9 lub 233 daje znak „é”, więc to właśnie wyświetla terminal.
(2) python próbuje niejawnie zakodować ciąg znaków Unicode za pomocą dowolnego schematu ustawionego w sys.stdout.encoding, w tym przypadku jest to „UTF-8”. Po zakodowaniu UTF-8 otrzymany ciąg binarny to „\ xc3 \ xa9” (zobacz późniejsze wyjaśnienie). Terminal odbiera strumień jako taki i próbuje zdekodować kod 0xc3a9 przy użyciu latin-1, ale latin-1 przechodzi od 0 do 255, a więc dekoduje tylko strumienie 1 bajt na raz. 0xc3a9 ma długość 2 bajtów, dekoder latin-1 interpretuje go zatem jako 0xc3 (195) i 0xa9 (169), co daje 2 znaki: Ă i ©.
(3) Python koduje punkt kodowy Unicode u '\ xe9' (233) ze schematem latin-1. Okazuje się, że zakres punktów kodowych Latin-1 to 0-255 i wskazuje dokładnie ten sam znak, co Unicode w tym zakresie. Dlatego punkty kodowe Unicode w tym zakresie będą dawały tę samą wartość, gdy zostaną zakodowane w latin-1. Więc u '\ xe9' (233) zakodowane w latin-1 również da ciąg binarny '\ xe9'. Terminal otrzymuje tę wartość i próbuje dopasować ją na mapie znaków latin-1. Podobnie jak przypadek (1), zwraca „é” i to jest wyświetlane.
Zmieńmy teraz ustawienia kodowania terminala na UTF-8 z menu rozwijanego (tak jakbyś zmienił ustawienia kodowania przeglądarki internetowej). Nie ma potrzeby zatrzymywania Pythona ani restartowania powłoki. Kodowanie terminala jest teraz zgodne z kodowaniem Pythona. Spróbujmy wydrukować ponownie:
>>> print '\xe9' # (4)
>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)
>>>
(4) Python wyprowadza ciąg binarny bez zmian. Terminal próbuje zdekodować ten strumień za pomocą UTF-8. Ale UTF-8 nie rozumie wartości 0xe9 (patrz późniejsze wyjaśnienie) i dlatego nie jest w stanie przekonwertować jej na punkt kodowy Unicode. Nie znaleziono punktu kodowego, brak wydrukowanego znaku.
(5) Python próbuje niejawnie zakodować ciąg znaków Unicode za pomocą tego, co jest w sys.stdout.encoding. Nadal „UTF-8”. Wynikowy ciąg binarny to „\ xc3 \ xa9”. Terminal odbiera strumień i próbuje zdekodować 0xc3a9 również przy użyciu UTF-8. Zwraca wartość kodu 0xe9 (233), która na mapie znaków Unicode wskazuje na symbol „é”. Terminal wyświetla „é”.
(6) Python koduje ciąg znaków Unicode za pomocą latin-1, daje ciąg binarny o tej samej wartości '\ xe9'. Ponownie, w przypadku terminala jest to prawie to samo, co w przypadku (4).
Wnioski: - Python wyprowadza ciągi nie-Unicode jako surowe dane, bez uwzględnienia domyślnego kodowania. Terminal po prostu wyświetla je, jeśli jego bieżące kodowanie jest zgodne z danymi. - Python wyprowadza ciągi Unicode po zakodowaniu ich przy użyciu schematu określonego w sys.stdout.encoding. - Python pobiera to ustawienie ze środowiska powłoki. - terminal wyświetla dane wyjściowe zgodnie z własnymi ustawieniami kodowania. - kodowanie terminala jest niezależne od powłoki.
Więcej szczegółów na temat Unicode, UTF-8 i latin-1:
Unicode to w zasadzie tablica znaków, w której niektóre klawisze (punkty kodowe) zostały tradycyjnie przypisane do wskazania niektórych symboli. np. konwencją zdecydowano, że klucz 0xe9 (233) jest wartością wskazującą na symbol „é”. ASCII i Unicode używają tych samych punktów kodowych od 0 do 127, podobnie jak latin-1 i Unicode od 0 do 255. Oznacza to, że 0x41 wskazuje „A” w ASCII, latin-1 i Unicode, 0xc8 wskazuje „Ü” w latin-1 i Unicode, 0xe9 wskazuje na „é” w latin-1 i Unicode.
Podczas pracy z urządzeniami elektronicznymi punkty kodowe Unicode wymagają wydajnego sposobu reprezentacji elektronicznej. O to chodzi w schematach kodowania. Istnieją różne schematy kodowania Unicode (utf7, UTF-8, UTF-16, UTF-32). Najbardziej intuicyjnym i prostym podejściem do kodowania byłoby po prostu użycie wartości punktu kodowego w mapie Unicode jako wartości jej formy elektronicznej, ale Unicode ma obecnie ponad milion punktów kodowych, co oznacza, że niektóre z nich wymagają 3 bajtów wyrażone. Aby efektywnie pracować z tekstem, mapowanie 1 do 1 byłoby raczej niepraktyczne, ponieważ wymagałoby, aby wszystkie punkty kodowe były przechowywane w dokładnie tej samej ilości miejsca, z minimum 3 bajtami na znak, niezależnie od ich rzeczywistych potrzeb.
Większość schematów kodowania ma wady związane z wymaganiami dotyczącymi miejsca, najbardziej ekonomiczne nie obejmują wszystkich punktów kodowych Unicode, na przykład ascii obejmuje tylko pierwsze 128, podczas gdy latin-1 obejmuje pierwsze 256. Inne, które starają się być bardziej wszechstronne, również kończą marnotrawstwo, ponieważ wymagają więcej bajtów niż to konieczne, nawet w przypadku typowych „tanich” znaków. Na przykład UTF-16 wykorzystuje co najmniej 2 bajty na znak, w tym te z zakresu ascii („B”, które wynosi 65, nadal wymaga 2 bajtów pamięci w UTF-16). UTF-32 jest jeszcze bardziej marnotrawny, ponieważ przechowuje wszystkie znaki w 4 bajtach.
UTF-8 sprytnie rozwiązał ten dylemat za pomocą schematu zdolnego do przechowywania punktów kodowych ze zmienną ilością przestrzeni bajtowych. W ramach swojej strategii kodowania UTF-8 łączy punkty kodowe z bitami flag, które wskazują (prawdopodobnie dekoderom) ich wymagania dotyczące przestrzeni i ich granice.
Kodowanie UTF-8 punktów kodowych Unicode w zakresie ascii (0-127):
0xxx xxxx (in binary)
- znaki x pokazują rzeczywistą przestrzeń zarezerwowaną do „przechowywania” punktu kodowego podczas kodowania
- Początkowe 0 to flaga wskazująca dekoderowi UTF-8, że ten punkt kodowy będzie wymagał tylko 1 bajtu.
- po zakodowaniu UTF-8 nie zmienia wartości punktów kodowych w tym konkretnym zakresie (tj. 65 zakodowanych w UTF-8 to również 65). Biorąc pod uwagę, że Unicode i ASCII są również kompatybilne w tym samym zakresie, nawiasem mówiąc, sprawia, że UTF-8 i ASCII są również kompatybilne w tym zakresie.
np. punkt kodowy Unicode dla „B” to „0x42” lub 0100 0010 w postaci binarnej (jak powiedzieliśmy, jest to to samo w ASCII). Po zakodowaniu w UTF-8 staje się:
0xxx xxxx <-- UTF-8 encoding for Unicode code points 0 to 127
*100 0010 <-- Unicode code point 0x42
0100 0010 <-- UTF-8 encoded (exactly the same)
Kodowanie UTF-8 punktów kodowych Unicode powyżej 127 (nie ASCII):
110x xxxx 10xx xxxx <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx <-- (from 2048 to 65535)
- początkowe bity „110” wskazują dekoderowi UTF-8 początek punktu kodowego zakodowanego w 2 bajtach, podczas gdy „1110” oznacza 3 bajty, 11110 oznaczałoby 4 bajty i tak dalej.
- wewnętrzne bity flagi „10” są używane do sygnalizowania początku wewnętrznego bajtu.
- ponownie x oznaczają miejsce, w którym wartość punktu kodu Unicode jest przechowywana po zakodowaniu.
np. „é” Punkt kodowy Unicode to 0xe9 (233).
1110 1001 <-- 0xe9
Kiedy UTF-8 koduje tę wartość, określa, że wartość jest większa niż 127 i mniejsza niż 2048, dlatego powinna być zakodowana w 2 bajtach:
110x xxxx 10xx xxxx <-- UTF-8 encoding for Unicode 128-2047
***0 0011 **10 1001 <-- 0xe9
1100 0011 1010 1001 <-- 'é' after UTF-8 encoding
C 3 A 9
Punkty kodowe 0xe9 Unicode po kodowaniu UTF-8 stają się 0xc3a9. Dokładnie tak, jak odbiera go terminal. Jeśli twój terminal jest ustawiony na dekodowanie łańcuchów przy użyciu latin-1 (jednego ze starszych kodowań innych niż Unicode), zobaczysz à ©, ponieważ tak się składa, że 0xc3 w Latin-1 wskazuje na à i 0xa9 na ©.