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:

  1. 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)
  2. umjesto nekih znakova proširenog ASCII kôda (128-255). (vidi http://en.wikipedia.org/wiki/ISO/IEC_8859)
  3. 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:

  1. u editoru to odabrati kao opciju.
  2. 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