Tech ramblings by Marcin

Unicode w Pythonie

2010-04-21 22:54

Szlag by to trafił! Czemu tak ciężko jest obsługiwać unicode w pythonie?

No niby wszystko jest ładnie konwertowane wewnętrznie na odpowiednie reprezentacje, ale jeśli przychodzi co do czego, i chcemy wypisać sobie taki ciąg znaków na ekran, to już jest ciężej.

Weźmy prosty przykład. Wczytywałem plik z polskimi znaczkami, o taki:

Roger Macdonald;Człowiek w żelaznej masce;25

a potem w kodzie pythonowym robiłem sobie tak:

for line in open(sys.argv[1],'r').readlines():
print line.decode('utf-8')
(.... tu wysłanie do jakiegoś tam Web Service'u)

i wszystko się pięknie wysypywało z komunikatem:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc5 in position 549: ordinal not in range(128)

Błąd był niby zwracany przez bibliotekę do WS'ów, ale co z tego? Oczywiście słuszność miał python, że rzucił takim wyjątkiem, bo kodowanie źle miałęm ustawione:

>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>>

Żeby temu zaradzić należało:

  • stworzyć plik _sitecustomize.py_ z zawartością jak niżej:

    #sitecustomize.py
    import sys
    sys.setdefaultencoding('utf-8')

  • ustawić _PYTHONPATH=._ bo inaczej interpreter nie wczytywał pliku.

Strasznie to zagmatwane i hakerskie. Więcej o problemie jest tutaj:

Comments

dajcie spokoj... python 2.7 nie potrafi dodac do siebie dwoch unicodow - to jest porazka - w 2 dekadzie 21 wieku nie mozna dodac do siebie dwoch zmiennych tego samego typu?
ehh

Niestety musisz się przyzwyczaić, że często podczas używania pythona będziesz zmuszony rozwiązywać problemy których w normalnych językach zwyczajnie nie ma.

@Lionix: a to byłby dobry trop, nawet nie wiedziałem, że tak można robić, tyle że w nagłówku pliku mam taki wpis:

#!/usr/bin/env/python
# -*- coding: utf-8 -*-

a i tak zaraz po starcie skryptu dostaję 'ascii' przy wywołaniu sys.getdefaultencoding()

>Błąd był niby zwracany przez bibliotekę do WS'ów, ale co z tego?

A no wszystko z tego, to nie wina Pythona że ta biblioteka przeładowując sys.stdout zmienia domyślne kodowanie UNICODE na ASCII.

Inna sprawa że konwerter ASCII zamiast wypisywać np '?' wywala błąd, ale akurat ja to uznaje za plus bo nie chciał byś widzieć chyba ? zamiast polskich literek i szukać gdzie one się tworzą?

@balon777: jak się otwiera stronę w przeglądarce i zabiera za jej czytanie odrobinę później, to może się zdarzyć że lista komentarzy na niej jest już trochę nieaktualna... ale widać że o tym samym pomyśleliśmy ;)

P.S.
W pythonie 3.x parametr encoding obsługuje standardowa funkcja open(), nie trzeba korzystać z modułu codecs

@demikaze: Widzę, że czytałeś poprzednie komenty ;)

Piszę z pamięci więc potraktuj to jedynie jako wskazówkę, ale zakładając że plik z polskimi znaczkami zakodowany jest w utf8 to w twoim przykładzie należałoby go otworzyć w taki sposób:

import codecs
...
for line in codecs.open(sys.argv[1],'r',encoding='utf-8').readlines():
...

Jeśli plik z polskimi znaczkami jest w kodowaniu cp1250 to wystarczy:

import codecs, sys

for line in codecs.open(sys.argv[1],"r", "cp1250"):
# teraz line to obiekt klasy unicode
print line.encode("utf8")

w 2.6 też "coding: utf-8" załatwia sprawę ;)

Trzeba używać Pythona >=3.0, albo Perla nie więcej niż ośmioletniego :>

Hm, mi w Pythonie 2.6 setdefaultencoding nie działało jak wrzuciłem w kod. Trzeba do oddzielnego pliku?

Już dawno w Pythonie nie siedzę i nie mam jak tego sprawdzić ale zwykłe wrzucenie w nagłówek # -*- coding: utf-8 -*- nie rozwiązuje Twojego problemu?