Article View: pl.comp.lang.python
Article #27526Re: String, unicode, dziwny problem i 5 pytań - długie
From: Jan Kaliszewski
Date: Fri, 18 Jun 2010 18:36
Date: Fri, 18 Jun 2010 18:36
283 lines
13015 bytes
13015 bytes
Piotr Pastuszka <piotr.pastuszka@wp.pl> napisał (2010-06-16):
[...]
> PYTANIE 1.
> Co powoduje, że powyższy kod uruchamiany na tym samym komputerze, ale u
> różnych użytkowników działa dobrze lub źle.
Ustawienia kodowania znaków terminala/wiersza poleceń -- czy mówiąc
ogólniej -- środowiska, w którym jest uruchomiany Python. Podany przez
Ciebie kod wysyła na zewnątrz tekst zakodowany kodowaniem utf-8, więc
prawidłowe znaki wyświetlną się tylko, jeżeli środowisko (terminal/wiersz
poleceń/IDE, w którym osadzony jest python etc.) też ma ustawione utf-8
(no i jeżeli font, którego owe środowisko używa, zawiera znaki, których
użyłeś.
> PYTANIE 2.
> Co powoduje, że w niektórych edytora powyższy kod działa "ładnie", a na
> niektórych "nieładnie"
Patrz punkt 1.
> "
> Łańcuch znaków przechowuje znaki w zakodowanej postaci np. w systemie
> UTF-8,
> ISO 8859-1, czy ISO 8859-4; może być wieloznaczny. Natomiast unikod jest
> jednoznaczny, więc nie jest zakodowany w tego typu systemach kodowania.
> Ponieważ łańcuch znaków jest zakodowany, musimy odkodować (za pomocš
> decode), aby otrzymać niezakodowany unikod. Z kolei unikod, ponieważ jest
> niezakodowany, musimy zakodować (za pomocą encode), aby otrzymać
> zakodowany łańcuch znaków.
> "
Święta prawda. W Pytonie 2.x są dwa typu napisowe: str i unicode. Obiekt
str to sekwencja elementów, z których każdy reprezentowany jest przez
1 bajt czyli liczbę całkowitą od 0 do 255. A więc, by zinterpretować
obiekt str jako tekst zawierający np. polskie litery, musimy określić
jakiego *kodowania* (czyli jakie wartości lub ciągi wartości oznaczają
jakie znaki) użyjemy do jego odczytu; tak samo przy zapisie: musimy
określić *kodowanie*, czyli -- jakiej literze (znakowi) odpowiada jaka
wartość liczbowa. Przy czym są kodowania, jak np. iso-8859-2 czy cp-1250,
które wszystkim objętym przez siebie znakom przyporządkowują 1-bajtowe
wartości; a są też kodowania, takie jak utf-8 czy utf-16, w przypadku
których 1 znak może być zakodowany za pomocą kilku bajtów (np.
w przypadku utf-8 litera 'a' kodowana jest, tak jak w ASCII, za pomocą
wartości 97, ale już np. litera 'ń' -- za pomocą ciągu: 197, 132
[w zapisie szestanstkowym: 0xc5, 0x84]).
Obiekt unicode to sekwencja elementów, z których każdy reprezentowany
jest przez tzw. code point czyli liczbę całkowitą od 0 do wielu wielu
tysięcy (ilu dokładnie, to zależy od wersji standaru Unicode i innych
detali, którymi w tym momencie się nie zajmuję). A więc dany kod (code
point) odpowiada zawsze temu samemu znakowi -- określa to standard
Unicode, np. literze 'ń' odpowiada liczba 324 czuli 0x144 w zapisie
szesnastkowym (patrz: http://www.decodeunicode.org/de/u+0144). Dzięki
dużej liczbie możliwych kodów (code point'ów), standard Unicode obejmuje
znaki z bardzo wielu (jeżeli nawet nie wszystkich, to prawie wszystkich)
języków świata.
Kilka wniosków i spostrzeżeń:
* gdy przerabiamy obiekt unicode na obiekt str -- kodujemy go, np.:
napis_str = napis_unicode.encode('cp-1250')
* gdy przerabiamy obiekt str na obiekt unicode -- dekodujemy go, np.:
napis_unicode = napis_str.decode('cp-1250')
lub
napis_unicode = unicode(napis_str, 'cp-1250')
* unicode i utf-8 to dwie różne rzeczy! (to pierwsze to pythonowy typ
obiektów napisowych zawierających znaki określone standardem Unicode;
to drugie to sposób kodowania takich znaków, jeżeli są przechowywane
w postaci obiektu str).
> Czyli moje oznaczanie plików UTF-8 NIC NIE ZNACZY! Ze dopóki nie wskaże
> Pythonowi, że chcę na wyjściu mieć kod unicode to muszę albo mieć na
> wejściu unicod, lub ODCZYTAC/ODSZYFROWAĆ (decode) zapisany teks do
> kodu unicod.
> PYTANIE 4
> Czemu musze robić np ("Gotówka wysłane").decode('utf-8') skoro jest
> nagówek
> #-*- coding:utf-8 -*-
> Przecież ten nagłówek jawnie wskazuje, że mam 'utf-8' więc po co muszę to
> wskazywać w kodzie? czemu sam python tego nie zauważa ?.
> Uogólniajšc co robi kontrukcja #-*- coding:utf-8 -*- ?
> Gdybym miał strzelać powiedziałby, że dla Pythona nie znaczy NIC, że to
> informacja dla EDYTORA.
Oznaczenie na początku pliku typu # -*- coding: utf-8 -*- informuje Pythona
jakiego kodowania ma użyć, by odczytać Twój kod źródłowy (który wszak jest
plikiem, a więc w sensie logicznym -- tylko ciągiem bajtów). Deklaracja ta
musi być zgodna z faktycznym kodowaniem, jakiego dostarcza edytor tekstu,
za pomocą którego piszesz kod (inaczej Python albo od razu wywali błąd,
albo zinterpretuje Twój kod niezgodnie z Twoją intencją). Raz jeszcze
podkreślę: ta deklaracja na początku pliku dotyczy *wyłącznie* kodowania
*kodu źródłowego* Twojego programu, i niczego innego w tym programie.
A więc, jeżeli Twój edytor jest ustawiony na utf-8 i jeżeli napiszesz:
# -*- coding: utf-8 -*-
print "żółć"
...twój program prześle na wyjście ciąg bajtów, będący napisem "żółć"
zakodowanym kodowaniem utf-8 (0xc5, 0xbc, 0xc3, 0xb3, 0xc5, 0x82, 0xc4,
0x87)
Natpmiast, jeżeli Twój edytor jest ustawiony na iso-8859-2 i napiszesz:
# -*- coding: iso-8859-2 -*-
print "żółć"
...twój program prześle na wyjście ciąg bajtów, będący napisem "żółć"
zakodowanym kodowaniem iso-8859-2 (0xbf, 0xf3, 0xb3, 0xe6).
Oczywiście, tak jak napisałem wyżej, terminal/wiersz poleceń/środowisko
programistyczne, pod którym uruchamiasz program wypisze właściwe znaki
tylko wtedy jeżeli skonfigurowane jest pod takie a nie inne kodowanie.
Czyżby stworzenie programu uruchamialnego na różnych maszynach jest
skomplikowane i wymaga czegoś jak poniżej?
# -*- coding: iso-8859-2 -*-
kodowanie = raw_inpit('Podaj kodowanie Twojego srodowiska:')
print "żółć".decode('iso-8859-2').encode(kodowanie)
Na szczęście Python sam stara się dowiedzieć, jakiego kodowania
używa środowisko (terminal, wiersz poleceń bądź IDE czyli środowisko
programistyczne takie jak SPE...) w którym został uruchomiony --
informację tę można pobrać z sys.stdout.encoding, np.
# -*- coding: iso-8859-2 -*-
import sys
print "żółć".decode('iso-8859-2').encode(sys.stdout.encoding)
Ale nie dość na tym. print jest na tyle mądre, że jeżeli podamy mu
obiekt unicode, a nie str, zakoduje ten obiekt kodowaniem takim,
jakie jest w sys.stdout.encoding -- a więc możemy napisać:
# -*- coding: iso-8859-2 -*-
print "żółć".decode('iso-8859-2') # przekazujemy obiekt unicode
Przy czym -- to osobna sprawa, ale warto ją zauważyć i stosować gdzie
się da -- zamiast tworzyć obiekt str (z str-owego literału "żółć")
i następnie go dekodować do postaci unicode za pomocą kodowania jakim
kodowane są źródłą naszego programu -- możemy od razu utworzyć obiekt
unicode (z unicodowego literału u"żółć"):
Mówiąc bardziej ogólnie:
# -*- coding: JAKIES_KODOWANIE_ZRODEL -*-
.......
....... "żółć".decode(JAKIES_KODOWANIE_ZRODEL) .......
.......
jest równoznaczne z:
# -*- coding: JAKIES_KODOWANIE_ZRODEL -*-
.......
....... u"żółć" .......
.......
A wracając do naszego przykładu:
# -*- coding: iso-8859-2 -*-
print u"żółć"
Przekaże na wyjśćie ciąg bajtów będącym napisem "żółć" zakodowanym
takim kodowaniem, jakie Python wykrył i zapisał w sys.stdout.encoding.
Oczywiście może się zdarzyć, że w sys.stdout.encoding znajdzie się
kodowanie nie obsługujące podanych znaków -- np. gdy standardowe wyjście
przekierowano do pliku (sys.stdout.encoding zawiera wówczas kodowanie
ascii); powyższy kod zwróci wówczas błąd:
UnicodeEncodeError: 'ascii' codec can't encode characters in position
0-3: ordinal not in range(128)
Więc w programach, które nie są prostymi skryptami czy programikami
do użytku własnego i znajomych, można dać np.:
# -*- coding: iso-8859-2 -*-
try:
print u"żółć"
except UnicodeEncodeError:
print u"żółć".encode(errors=ignore)
# ewentualnie: print u"żółć".encode('unicode_escape')
lub nawet:
# -*- coding: iso-8859-2 -*-
import locale
KODOWANIE_Z_LOCALE = locale.getpreferredencoding()
try:
print u"żółć"
except UnicodeEncodeError:
print u"żółć".encode(KODOWANIE_Z_LOCALE, errors=ignore)
> ========================
> print unicode("Gotówka wysłane",'utf-8') odszyfrowanie do unicode stringu
> zaszyfrowanego UTF-8
> ^
> SyntaxError: invalid syntax
> Script terminated.
> ========================
[w związku z pytaniem 3]
Przypuszczam, że:
* albo zapomniałeś, by przed komentarzem "odszyfrowanie do unicode
stringu..." dać znacznik komentarza #
* albo uruchomiłeś Pythona 3.x, który nie ma słowa kluczowego print, ma
natomiast funkcję print() (pierwsza rzucająca się w oczy różnica, jest
taka, że zawsze trzeba używać nawiasów)
* albo zadeklarowane kodowanie źródeł programu jest niezgodnie z
kodowaniem faktycznie używanym przez edytor, pod którym pisałeś kod
(nie sądzę, żeby to było to, ale teoretycznie w pewnych sytuacjach też
może to spowodować błąd składniowy).
> PYTANIE 5
> W którymś poście na grupie pojawia zapis:
> sys.setdefaultencoding('utf8')
> czyli coś o czym wcześniej nie czytałem. i pytanie co robi ten kod?
W 99% przypadków nie należy tego ruszać (zresztą celowo jest to
utrudnione -- poprzez usuwanie tej funkcji z modułu sys zaraz po
pierwszym jej użyciu w module site...).
Funkcja ta ustawia tzw. domyślne kodowanie Pythona -- dla sytuacji,
w których programista nie określił, jakie kodowanie ma być użyte do
zakodowania/rozkodowania... Np. jeżeli użyjemy encode()/decode()
bez podawania kodowania, lub jeżeli (jest wiele takich sytuacji)
Python dostał unicode, a spodziewał się str, lub jeżeli dostał str, a
spodziewał się unicode -- sam niejawnie rozkodowuje/zakodowuje dane
by uzyskać właściwy typ; używa wtedy owego domyślnego kodowania.
Domyślne kodowanie jest standardowo ustawione na ascii (obejmje
tylko najbardziej standardowe 128 znaków: alfabet łaciński,
podstawowe znaki matematyczne i przestankowe, podstawowe kody
kontrolne, np. znak końca wiersza...), co oznacza, że np. kod:
u"żółć".encode()
...spowoduje wystąpienie wyjątku UnicodeEncodeError.
----
Na koniec kilka morałów:
* w odniesieniu do tekstu -- wszędzie gdzie się da, należy używać
obiektów unicode a nie str, a jedynie na wejściu/wyjściu
odkodowywać/zakodowywać z/do postaci ciągu bajtów (str),
* na dłuższą metę lepiej podawać kodowanie jawnie ("explicit is
better than implicit" -- Zen of Python) i raczej unikać sytuacji,
w których Python będzie dokonywać automatycznego-niejawnego
kodowania/dekodowania (bo gdy wyskoczy błąd związany z kodowaniem,
często trudno potem dojść, gdzie tak naprawdę nastąpiło
zniekształcenie danych...).
Pozdrawiam,
Jan Kaliszewski (zuo)
PS. UWAGA: cała moja odpowiedź dotyczy Pythona 2.x. W Pythonie 3.x
rzeczy wyglądają trochę inaczej: dawny typ unicode nazywa się str,
a dawnego str nie ma. Jest za to typ bytes (i pokrewny mu bytearray...),
do pewnego stopnia podobny do dawnego str, ale nieprzeznaczony do
operowania danymi tekstowymi, a jedynie binarnymi (ale to inna historia).
W Pythonie 3.x nie ma też niejawnego automatycznego kodowania/dekodowania
-- doświadczenia Pythona 2.x pokazują, że sprawiało ono wiele kłopotów.
W Pythonie 3.x programista, jeżeli poda dane złego typu otrzyma jasny
komunikat błędu, a nie nieprawidłowe wyniki zmuszające do bawienia się
w Sherlocka Holmesa ("w którym momencie Python dokonał tego niejawnego
kodowania/dekodowania?").
--
Jan Kaliszewski (zuo)
Technical Details
Message-ID:
<op.veidzb06fvx12t@jerzozwiesz.localdomain>Path:
mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.icm.edu.pl!plix.pl!newsfeed1.plix.pl!nntp.aster.pl!newsread1.aster.pl!not-for-mailReferences:
<hvb91g$rpp$1@node2.news.atman.pl>Show Full Headers
Complete Article Headers
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.icm.edu.pl!plix.pl!newsfeed1.plix.pl!nntp.aster.pl!newsread1.aster.pl!not-for-mail From: "Jan Kaliszewski" <zuo@chopin.edu.pl> Newsgroups: pl.comp.lang.python Subject: Re: String, unicode, dziwny problem i 5 pytań - długie Date: Fri, 18 Jun 2010 20:36:37 +0200 Organization: Aster Sp. z o.o. Lines: 283 Message-ID: <op.veidzb06fvx12t@jerzozwiesz.localdomain> References: <hvb91g$rpp$1@node2.news.atman.pl> NNTP-Posting-Host: 62.121.72.3 Mime-Version: 1.0 X-Trace: newsread1.aster.pl 1276886060 19735 62.121.72.3 (18 Jun 2010 18:34:20 GMT) X-Complaints-To: abuse@aster.pl NNTP-Posting-Date: Fri, 18 Jun 2010 18:34:20 +0000 (UTC) User-Agent: Opera Mail/10.10 (Linux) Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit