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:
Kodowanie : znaki → bajty
Dekodowanie : bajty → znaki
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
pomimo s
posiadania jednego postrzeganego przez użytkownika (koreański) znak (ponieważ jest reprezentowany przez 3 punkty kodowe - nawet jeśli nie musi, jak print("\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łu u"…"
), 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łu b"…"
). 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:
% python
Python 2.7.6 (default, Nov 15 2013, 15:20:37)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.stdout.encoding
UTF-8
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):
% python2.7 -c "import sys; print sys.stdout.encoding" | cat
None
% python3.4 -c "import sys; print(sys.stdout.encoding)" | cat
UTF-8
W razie potrzeby kodowanie stdin, stdout i stderr można jednak ustawić za pomocą PYTHONIOENCODING
zmiennej środowiskowej:
% PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat
UTF-8
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:
import codecs
import locale
import sys
# Wrap sys.stdout into a StreamWriter to allow writing unicode.
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)
uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni
W przypadku Pythona 3 możesz sprawdzić jedno z pytań zadanych wcześniej w StackOverflow.