Dlaczego Python drukuje znaki unicode, gdy domyślnym kodowaniem jest ASCII?

Z powłoki Pythona 2.6:

>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'\xe9'
é
>>> 

Spodziewałem się, że po instrukcji print pojawi się jakiś bełkot lub błąd, ponieważ znak " é " nie jest częścią ASCII i nie podałem kodowania. Chyba nie rozumiem, co oznacza ASCII jako domyślne kodowanie.

EDIT

Przeniosłem edycję do sekcji odpowiedzi i zaakceptowałem ją zgodnie z sugestią.

Author: Michael Ekoka, 2010-04-08

6 answers

Dzięki fragmentom z różnych odpowiedzi, myślę, że możemy zszyć Wyjaśnienie.

Próbując wydrukować ciąg znaków unicode, u'\xe9', Python domyślnie próbuje zakodować ten ciąg za pomocą schematu kodowania aktualnie przechowywanego w sys.stdout.kodowanie. Python pobiera to ustawienie ze środowiska, z którego zostało zainicjowane. Jeśli nie może znaleźć odpowiedniego kodowania ze środowiska, dopiero wtedy powróci do swojego domyślnego, ASCII.

Na przykład, I użyj powłoki bash, która koduje domyślnie UTF-8. Jeśli uruchamiam Pythona od niego, to pobiera i używa tego ustawienia:

$ python

>>> import sys
>>> print sys.stdout.encoding
UTF-8

Zakończmy na chwilę powłokę 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 uruchom powłokę Pythona ponownie i sprawdź, czy rzeczywiście przywraca domyślne kodowanie 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ś uzyskać ładny błąd wiadomość

>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' 
in position 0: ordinal not in range(128)

Pozwala wyjść z Pythona i odrzucić powłokę bash.

Będziemy teraz obserwować, co dzieje się po wysłaniu ciągów przez Pythona. W tym celu najpierw uruchomimy powłokę bash w terminalu graficznym (używam terminala Gnome) i ustawimy terminal tak, aby dekodował wyjście za pomocą ISO-8859-1 aka latin-1 (terminale graficzne zwykle mają opcję ustawić kodowanie znaków w jednym z rozwijanych menu). Zauważ, że nie zmienia to rzeczywistej powłoki environment's encoding, zmienia tylko sposób, w jaki terminal sam dekoduje dane wyjściowe, tak jak robi to przeglądarka internetowa. Można więc zmienić kodowanie terminala, niezależnie od środowiska powłoki. Zacznijmy Pythona od powłoki i sprawdźmy, czy sys.stdout.kodowanie 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 wyprowadza łańcuch binarny tak, jak jest, terminal odbiera go i próbuje dopasować jego wartość do latin-1 Mapa postaci. W języku łacińskim-1, 0xe9 lub 233 daje znak " é " i tak właśnie wyświetla terminal.

(2) python próbuje w domyśle zakodować Łańcuch Unicode za pomocą dowolnego schematu aktualnie ustawionego w sys.stdout.kodowanie, w tym przypadku jest to "UTF-8". Po kodowaniu UTF-8 otrzymany ciąg binarny to '\xc3\xa9 ' (patrz późniejsze Wyjaśnienie). Terminal odbiera strumień jako taki i próbuje dekodować 0xc3a9 używając latin-1, ale latin-1 przechodzi z 0 do 255 i tak tylko dekoduje strumienie 1 bajt na raz. 0xc3a9 ma długość 2 bajtów, dekoder latin-1 interpretuje go jako 0xc3 (195) i 0xa9 (169), co daje 2 znaki: Ã i ©.

(3) python koduje punkt kodu unicode u'\xe9' (233) ze schematem latin-1. Okazuje się, że zakres punktów kodu latin-1 wynosi 0-255 i wskazuje dokładnie ten sam znak, co Unicode w tym zakresie. Dlatego punkty kodu Unicode w tym zakresie dają tę samą wartość, gdy są zakodowane w latin-1. So u '\xe9 ' (233) latin-1 daje również ciąg binarny '\xe9'. Terminal odbiera tę wartość i próbuje dopasować ją na mapie znaków latin-1. Podobnie jak case (1), daje "é" i to jest wyświetlane.

Zmieńmy teraz ustawienia kodowania terminala na UTF-8 z rozwijanego menu (tak jak zmienisz ustawienia kodowania w przeglądarce internetowej). Nie ma potrzeby zatrzymywania Pythona ani restartowania powłoki. Kodowanie terminala odpowiada kodowaniu Pythona. spróbujmy jeszcze raz wydrukować:

>>> print '\xe9' # (4)

>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)

>>>

(4) python wypisuje binarny ciąg znaków tak, jak jest. Terminal próbuje dekodować 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 kodu unicode. Nie znaleziono kodu, nie wydrukowano znaków.

(5) python próbuje w domyśle zakodować Łańcuch Unicode za pomocą tego, co jest w sys.stdout.kodowanie. Nadal "UTF-8". Otrzymany ciąg binarny to '\xc3\xa9'. Terminal odbiera strumień i próby dekodowania 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 unicode za pomocą latin-1, daje ciąg binarny o tej samej wartości '\xe9'. Ponownie, dla terminala jest to prawie to samo co case (4).

Wnioski: - Python wysyła ciągi nie-unicode jako surowe dane, bez uwzględnienia jego domyślnego kodowania. Terminal po prostu się zdarza wyświetla je, jeśli jego obecne kodowanie pasuje do danych. - Python wyprowadza ciągi Unicode po zakodowaniu ich według schematu podanego w sys.stdout.kodowanie. - Python pobiera to ustawienie ze środowiska powłoki. - terminal wyświetla wyjście 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 jest w zasadzie tabelą znaków, w której niektóre klawisze (Punkty kodowe) zostały konwencjonalnie przypisane do wskazywania niektórych symboli. na przykład zgodnie z konwencją postanowiono, ż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 oznacza " a "w ASCII, latin-1 i Unicode, 0xc8 oznacza" Ü "w latin-1 i Unicode, 0xe9 oznacza" é " w latin-1 i Unicode.

Podczas pracy z urządzeniami elektronicznymi punkty kodu Unicode wymagają skuteczny sposób prezentacji elektronicznej. Na tym polega kodowanie. 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 na mapie Unicode jako wartości dla jego formy elektronicznej, ale obecnie Unicode ma ponad milion punktów kodowych, co oznacza, że niektóre z nich wymagają wyrażenia 3 bajtów. Aby efektywnie pracować z tekstem, mapowanie 1 do 1 byłoby raczej niepraktyczne, ponieważ wymagałoby to, aby wszystkie punkty kodu były przechowywane w dokładnie takiej samej ilości miejsca, z minimum 3 bajtami na znak, niezależnie od ich rzeczywistej potrzeby.

Większość schematów kodowania ma braki dotyczące wymagań dotyczących przestrzeni, najbardziej ekonomiczne nie obejmują wszystkich punktów kodu unicode, na przykład ascii obejmuje tylko pierwsze 128, podczas gdy latin - 1 obejmuje pierwsze 256. Inne, które starają się być bardziej kompleksowe kończy się również marnotrawstwem, ponieważ wymagają więcej bajtów niż to konieczne, nawet dla zwykłych "tanich" znaków. Na przykład UTF-16 używa co najmniej 2 bajtów na znak, w tym tych z zakresu ascii ('B', który 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, dzięki schematowi zdolnemu do przechowywania punktów kodu ze zmienną ilością spacji bajtowych. W ramach swojej strategii kodowania UTF-8 wskazuje kod z bitów flagowych, które wskazują (prawdopodobnie dekoderom) ich wymagania przestrzenne i ich granice.

Kodowanie UTF-8 punktów kodowych unicode w zakresie ascii (0-127):

0xxx xxxx  (in binary)
  • x pokazują rzeczywistą przestrzeń zarezerwowaną do" przechowywania " punktu kodu podczas kodowania
  • znacznik 0 jest znacznikiem wskazującym dekoderowi UTF-8, że ten punkt kodu będzie wymagał tylko 1 bajtu.
  • przy kodowaniu UTF-8 nie zmienia wartości punktów kodu w tym zakres określony (tzn. 65 kodowanych w UTF-8 to także 65). Biorąc pod uwagę, że Unicode i ASCII są również kompatybilne w tym samym zakresie, nawiasem mówiąc UTF - 8 i ASCII są również kompatybilne w tym zakresie.

Np. punkt kodu Unicode dla ' B ' to '0x42' lub 0100 0010 w binarnym (jak powiedzieliśmy, 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 kodu Unicode powyżej 127 (bez ascii):

110x xxxx 10xx xxxx            <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx  <-- (from 2048 to 65535)
  • wiodące bity '110' wskazuje dekoderowi UTF-8 początek punktu kodowego zakodowanego w 2 bajtach, podczas gdy "1110" wskazuje 3 bajty, 11110 wskazuje 4 bajty itd.
  • wewnętrzne bity znacznika ' 10 ' są używane do sygnalizowania początku wewnętrznego bajtu.
  • ponownie, x oznacza przestrzeń, w której wartość punktu kodu Unicode jest przechowywana po zakodowaniu.

Np. " é " punkt kodu Unicode to 0xe9 (233).

1110 1001    <-- 0xe9

Gdy UTF-8 zakoduje tę wartość, określa, że wartość większe niż 127 i mniejsze niż 2048, dlatego powinny być zakodowane 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 kodu 0xe9 Unicode po kodowaniu UTF-8 stają się 0xc3a9. Tak właśnie odbiera terminal. Jeśli twój terminal jest ustawiony na dekodowanie łańcuchów za pomocą latin-1( jednego ze starszych kodowań nie-unicode), zobaczysz é, ponieważ tak się składa, że 0xc3 w latin-1 wskazuje na Ã, a 0xa9 na ©.

 92
Author: Michael Ekoka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-23 13:09:38

Gdy znaki Unicode są drukowane na stdout, używane jest sys.stdout.encoding. Przyjmuje się, że znak nie-Unicode znajduje się w sys.stdout.encoding i jest po prostu wysyłany do terminala. Na moim systemie (Python 2):

>>> import unicodedata as ud
>>> import sys
>>> sys.stdout.encoding
'cp437'
>>> ud.name(u'\xe9') # U+00E9 Unicode codepoint
'LATIN SMALL LETTER E WITH ACUTE'
>>> ud.name('\xe9'.decode('cp437')) 
'GREEK CAPITAL LETTER THETA'
>>> '\xe9'.decode('cp437') # byte E9 decoded using code page 437 is U+0398.
u'\u0398'
>>> ud.name(u'\u0398')
'GREEK CAPITAL LETTER THETA'
>>> print u'\xe9' # Unicode is encoded to CP437 correctly
é
>>> print '\xe9'  # Byte is just sent to terminal and assumed to be CP437.
Θ

sys.getdefaultencoding() jest używany tylko wtedy, gdy Python nie ma innej opcji.

Zauważ, że Python 3.6 lub nowszy ignoruje kodowanie w systemie Windows i używa Unicode API do zapisu Unicode do terminala. Brak ostrzeżeń UnicodeEncodeError i prawidłowy znak jest wyświetlany, jeśli czcionka go obsługuje. Nawet jeśli czcionka nie obsługuje znaki mogą być nadal wycinane-N-wklejane z terminala do aplikacji z obsługującą czcionką i będzie poprawne. Upgrade!

 24
Author: Mark Tolonen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-29 02:25:17

REPL Pythona próbuje odczytać kodowanie z twojego środowiska. Jeśli znajdzie coś zdrowego, to wszystko po prostu działa. Kiedy nie może zrozumieć, co się dzieje, to się wkurza.

>>> print sys.stdout.encoding
UTF-8
 8
Author: Ignacio Vazquez-Abrams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-08 00:07:43

Ty określiłeś kodowanie wprowadzając jawny ciąg znaków Unicode. Porównaj wyniki nieużywania prefiksu u.

>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> '\xe9'
'\xe9'
>>> u'\xe9'
u'\xe9'
>>> print u'\xe9'
é
>>> print '\xe9'

>>> 

W przypadku \xe9 Python przyjmuje domyślne kodowanie (Ascii )i drukuje... coś pustego.

 4
Author: Mark Rushakoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-08 00:08:07

Zgodnie z domyślne/niejawne kodowanie i konwersje łańcuchów Pythona :

  • gdy print ing unicode, to encode D z <file>.encoding.
    • gdy encoding nie jest ustawione, unicode jest domyślnie konwertowane na str (ponieważ Kodek dla tego jest sys.getdefaultencoding(), tzn. ascii, dowolne znaki narodowe spowodowałyby UnicodeEncodeError)
    • dla standardowych strumieni, {[4] } jest wyprowadzany ze środowiska. Zazwyczaj ustawiane są strumienie fot tty (z ustawień regionalnych terminala), ale prawdopodobnie nie może być ustawiony na rury
      • więc print u'\xe9' prawdopodobnie zakończy się sukcesem, gdy wyjście jest do terminala, i nie powiedzie się, jeśli zostanie przekierowane. Rozwiązaniem jest encode() ciąg znaków z żądanym kodowaniem przed print ing.
  • gdy print ing str, bajty są wysyłane do strumienia tak, jak jest. To, jakie glify wyświetla terminal, zależy od jego ustawień regionalnych.
 0
Author: ivan_pozdeev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-01 06:35:25

U mnie działa:

import sys
stdin, stdout = sys.stdin, sys.stdout
reload(sys)
sys.stdin, sys.stdout = stdin, stdout
sys.setdefaultencoding('utf-8')
 -1
Author: user3611630,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-12 10:12:27