Unicode¶
Slova/znakovi su predstavljeni u memoriji pomoću brojeva. To pridruživanje se naziva kodiranje karaktera ili kodna stranica. Primjer je ASCII kôd koji definira kodove od 0-127 (npr. slovo ‘A’ ima kôd 65, slovo ‘B’ 66, itd.)
Slova s kvačicama nisu na tom popisu. Kroz povijest su se slovima s kvačicama dodjeljivali kodovi na više načina:
- umjesto nekih znakova ASCII kôda (0-127), npr.
ČĆĐŠŽ
umjesto^]\[@
, ačćđšž
umjesto~}|{\
. (postoje i druge nacionalne varijante http://en.wikipedia.org/wiki/ISO/IEC_646)- umjesto nekih znakova proširenog ASCII kôda (128-255). (vidi http://en.wikipedia.org/wiki/ISO/IEC_8859)
- unicode: kôd duljine 4 bytea pridružen svakom znaku/slovu iz bilo kojeg pisma (http://en.wikipedia.org/wiki/ISO_10646)
Mana ovog prvog pristupa je da se nije moglo u istom dokumentu imati istovremeno znakove kao \
, [
, {
i slova s kvačicama.
Situaciju donekle ispravlja drugi pristup u kojem također programi moraju znati koji skup kodova (kodna stranica) se koristi.
Treći način (unicode) omogućuje da se za sva pisma koristi ista kodna stranica. Međutim i tu postoji više varijanti zapisa. Razlikuju se po tome koliko se byteova koristi za zapis, kojim redoslijedom, itd., pa postoje tzv.: UCS-2, UCS-4, UTF-16, UTF-32, UTF-8, ...
Trenutno je najpopularniji način kodiranja UTF-8, koji koristi 1-6 byteova za kodiranje jednog znaka (broj byteova varira od znaka do znaka), a unazad je kompatibilan s ASCII standardom (to znači da je svaka ASCII tekst datoteka ujedno i UTF-8 datoteka). Također nikad ne koristi byte nula, tako da se neće nikad umjetno pojaviti null-terminator, pa će stoga funkcije iz C-a kao strcpy()
raditi ispravno. Također, vrijednosti kodova 0-127 se ne koriste za znakove koji zahtijevaju višebyteni zapis što znači da se npr. znak za novi red ne može slučajno pojaviti a to znači da editor koji ne zna da se radi o UTF-8 može točno odrediti koliko ima redaka teksta.
To su sve dobra svojstva u odnosu na ostale načine kodiranja, iako treba biti svjestan da prikaz znaka u UTF-8 (i u ostalim unicode kodiranjima) nije jednoznačan. Npr. slovo č posjeduje vlastiti kôd (NFC normalizacija), ali se č može prikazati kao kôd za c + kôd za kvačicu (NFD normalizacija). Više o normalizacijama na http://stackoverflow.com/a/7934397, http://www.unicode.org/reports/tr15. Dodatna komplikacija je da postoje i nizovi znakova koji kao niz imaju svoj kôd (npr. tri točke), pa neka pretvaranja iz normalizacije u normalizaciju mogu biti ireverzibilna.
Više na:
Da bi se omogućio rad s UTF-8 u pythonu potrebno je:
- u editoru to odabrati kao opciju.
- u samim python 2 programima dodati liniju koja označava da je kodiranje koje se koristi UTF-8. u pythonu 3 UTF-8 je default pa to nije potrebno.
U pythonu 3 nazivi varijabli mogu biti unicode znakovi, u pythonu 2 ne mogu. (Više na https://stackoverflow.com/a/46001544/2866169)
Sadržaj varijabli može biti niz unicode znakova i u pythonu 2 i u pythonu 3, ali postoje razlike:
Sadržaj niza | Python 2 | Python 3 | Ispis |
---|---|---|---|
Byteovi | Tip str ; unosi se pomoću ”...”; dopušteno “č” |
Tip bytes ; unosi se pomoću b”...” (potreban prefiks b); zabranjeno b”č” |
Prilikom ispisa python 2 pretvara niz byteova u niz unicode znakova. Python 3 ne pretvara niz byteova u unicode znakove nego samo ispisuje byteove. |
Unicode znakovi | Tip unicode ; unosi se pomoću u”...” (potreban prefiks u); dopušteno u”č” |
Tip str ; unosi se pomoću ”...”; dopušteno “č” |
U obje verzije pythona ispisuju se kao unicode znakovi |
U verziji 3 razlikuju se nizovi byteova od nizova unicode znakova. U pythonu 2 ta distinkcija nije bila toliko izražena jer se (kao što vidimo u tablici) prilikom ispisa byteovi pokušaju ispisati kao unicode znakovi.
U pythonu 2 zbunjuje što je i u unicode stringove i u nizove byteova dopušteno upisati unicode znakove.
U pythonu 3 je situacija pojednostavljena: nizovi byteova su uvijek byteovi, stringovi su uvijek nizovi unicode znakova.
Da bi se izbjegle moguće zabune u pythonu 3 je zabranjeno u nizove byteova direktno upisivati unicode znakove ako ih se nije prethodno kodiralo pomoću str.encode()
.
U pythonu 3 stringovi su nizovi unicode kodova.
Za određeni string i kodnu stranicu, pomoću str.encode()
možemo dobiti niz byteova koji taj string predstavlja.
Za određeni niz byteova i kodnu stranicu, pomoću bytes.decode()
možemo dobiti niz unicode znakova koji je predstavljen tim byteovima.
a = b'\xc4\x8d'
b = "č"
c = a.decode('utf8')
d = b.encode('utf8')
print(a, b, c, d, sep=" | ")
b'\xc4\x8d' | č | č | b'\xc4\x8d'
Razna kodiranja istog unicode znaka daju različite nizove byteova:
print('utf-8 ', "č".encode('utf-8'))
print('cp852 ', "č".encode('cp852'))
print('cp1250 ', "č".encode('cp1250'))
print('ISO-8859-2 ', "č".encode('ISO-8859-2'))
print('ISO-8859-16', "č".encode('ISO-8859-16'))
print('UTF-32 ', "č".encode('UTF-32'))
print('UTF-16 ', "č".encode('UTF-16'))
utf-8 b'\xc4\x8d'
cp852 b'\x9f'
cp1250 b'\xe8'
ISO-8859-2 b'\xe8'
ISO-8859-16 b'\xb9'
UTF-32 b'\xff\xfe\x00\x00\r\x01\x00\x00'
UTF-16 b'\xff\xfe\r\x01'
Umjesto 'č'.encode('utf8')
mogli smo koristiti bytes('č','utf8')
ili samo 'č'.encode()
jer je 'utf8'
default za str.encode()
u pythonu 3.
Popis kodiranja koje python podržava: https://docs.python.org/3/library/codecs.html#standard-encodings
Ako radimo s tekstualnom datotekom a ona nije u UTF-8 kodiranju,
u pravilu je potrebno otvoriti takvu datoteku navodeći argument encoding=...
prilikom pozivanja funkcije open()
. (Drugi način bi bio pročitati je kao binarnu a zatim koristiti bytes.decode()
.)
Na nekim operativnim sustavima nazivi datoteka su nizovi byteova (linux), a na nekim su nizovi unicode znakova (Windows). U slučaju da je naziv datoteke niz byteova koji se ne može “ispravno” dekodirati u niz unicode znakova (jer je kodiran u drugoj kodnoj stranici, ili je to proizvoljni niz byteova koji ne predstavlja unicode znakove) unicode zapis se komplicira (vidi https://www.python.org/dev/peps/pep-0383). Tada postoji mogućnost otvaranja datoteke pomoću naziva predstavljenog nizom byteova open(b'naziv.dat',...)
. Za detalje pogledati: http://www.dabeaz.com/python3io/MasteringIO.pdf
Modul unicodedata
nam može dati podatke o pojedinom unicode znaku:
import unicodedata
for c in "čćžšđ":
print(c, 'U+%04x' % ord(c), "%-16s" % c.encode(), unicodedata.category(c), unicodedata.name(c))
č U+010d b'\xc4\x8d' Ll LATIN SMALL LETTER C WITH CARON
ć U+0107 b'\xc4\x87' Ll LATIN SMALL LETTER C WITH ACUTE
ž U+017e b'\xc5\xbe' Ll LATIN SMALL LETTER Z WITH CARON
š U+0161 b'\xc5\xa1' Ll LATIN SMALL LETTER S WITH CARON
đ U+0111 b'\xc4\x91' Ll LATIN SMALL LETTER D WITH STROKE
Promjenom normalizacije možemo prebaciti prikaz npr. sa č na c + kvačica:
import unicodedata
for c in "č":
print('U+%04x' % ord(c), "%-16s" % c.encode(), unicodedata.category(c), unicodedata.name(c))
print()
for c in unicodedata.normalize("NFD","č"):
print('U+%04x' % ord(c), "%-16s" % c.encode(), unicodedata.category(c), unicodedata.name(c))
U+010d b'\xc4\x8d' Ll LATIN SMALL LETTER C WITH CARON
U+0063 b'c' Ll LATIN SMALL LETTER C
U+030c b'\xcc\x8c' Mn COMBINING CARON
Na sličan način možemo prebaciti npr. znak № u slova od kojih se on sastoji N i o:
import unicodedata
for c in "№":
print('U+%04x' % ord(c), "%-16s" % c.encode(), unicodedata.category(c), unicodedata.name(c))
print()
for c in unicodedata.normalize("NFKC","№"):
print('U+%04x' % ord(c), "%-16s" % c.encode(), unicodedata.category(c), unicodedata.name(c))
U+2116 b'\xe2\x84\x96' So NUMERO SIGN
U+004e b'N' Lu LATIN CAPITAL LETTER N
U+006f b'o' Ll LATIN SMALL LETTER O