Sposojeno predavanje o datotekah V2

Branje datotek

Delo z datotekami v Pythonu je tako preprosto, da ga že skoraj obvladamo.

Imejmo datoteko z imenom planeti.txt in takšno vsebino:

Merkur
Venera
Zemlja
Mars
Jupiter
Saturn
Uran
Neptun
Pluton

Če jo hočemo brati, jo moramo najprej odpreti.

>>> datoteka = open("planeti.txt")

Kaj se je zgodilo tule? open je, očitno, funkcija, ki kot argument dobi ime datoteke, ki jo hočemo odpreti. Kot rezultat vrne ... hm, datoteko? Glede na to, da smo rezultat funkcije priredili spremenljivki datoteka, lahko s print(datoteka) poškilimo vanjo in izvemo naslednje:

>>> print(datoteka)
<_io.TextIOWrapper name='planeti.txt' mode='r' encoding='UTF-8'>

Takole: datoteka je spremenljivka. Ni število (tipa int ali float), tudi ni tipa niz (str) ali seznam (list), temveč je tipa _io.TextIOWrapper. Pač zelo čudno ime tipa, predstavlja pa neko vrsto datoteke. Nadalje izvemo, da je datoteki, na katero se nanaša spremenljivka z imenom datoteka, ime 'planeti.txt', odprta je na način 'r' (spoiler: to pomeni, da je odprta za branje, read) in da je besedilo shranjeno v formatu UTF-8 (kaj je to, bomo že še izvedeli).

Tole ni bilo nič posebej informativnega. Kaj lahko počnemo z datoteko? Lahko jo prehodimo z zanko for.

>>> for vrstica in datoteka:
...    print(vrstica)
Merkur

Venera

Zemlja

Mars

Jupiter

Saturn

Uran

Neptun

Pluton

Če torej podtaknemo datoteko zanki for, jo bo zanka brala vrstico za vrstico.

A gremo še enkrat?

>>> for vrstica in datoteka:
...    print(vrstica)

Ne moremo. Ko preberemo datoteko do konca, je prebrana. Če jo hočemo ponovno brati, jo moramo tudi ponovno odpreti. (To ni povsem res, vendar je pri besedilnih datotekah dokaj res.)

Zakaj pa so vmes prazne vrstice? V datoteki jih vendar ni. Vse bo jasno, če datoteko preberemo nekoliko drugače. Kot vemo od prejšnjih predavanj, imajo seznami in nizi razne metode, ki z njimi počnejo različne stvari. Prav tako imajo svoje metode datoteke. Ena od njih je read, ki prebere celotno datoteko in vrne njeno vsebino kot niz.

>>> datoteka = open("planeti.txt")
>>> datoteka.read()
'Merkur\nVenera\nZemlja\nMars\nJupiter\nSaturn\nUran\nNeptun\nPluton\n'

(Mimogrede, metodi read lahko kot argument podamo številko, ki pove, koliko znakov datoteke naj prebere.)

Vidimo, da datoteka, jasno, vsebuje znake \n, ki povedo, kje se začenja nova vrstica. Ko beremo datoteko z zanko for, dobivamo celotne vrstice, skupaj z \n, torej, recimo 'Merkur\n'. Če poskusimo izpisati print('Merkur\n'), na koncu niza izpišemo prazno vrstico, še eno pa sam od sebe doda print. Odtod prazne vrstice.

Dva nauka. Prvi: strip je tvoj prijatelj - še prav posebej pri branju datotek.

>>> for vrstica in datoteka:
...    print(vrstica.strip())
Merkur
Venera
Zemlja
Mars
Jupiter
Saturn
Uran
Neptun
Pluton

Drugi: kadar po vrsticah berete besedilne datoteke, jih berite z zanko for. Torej tako

for vrstice in datoteka:

in ne tako

for vrstice in datoteka.read().split("\n"):

Drugo ni samo daljše in nepotrebno, temveč lahko tudi ne deluje. Če si že morate brez potrebe zapletati življenje, potem uporabite vsaj

for vrstice in datoteka.read().splitlines():

Poleg metode read ima datoteka še kar nekaj metod, med katerimi za zdaj omenimo readline, ki prebere eno vrstico, in readlines, ki prebere vse vrstice in vrne seznam.

Datoteke vedno beremo po vrsti. Kot pove njihovo ime, datoteke tečejo.

>>> datoteka = open("planeti.txt")
>>> datoteka.readline()
'Merkur\n'
>>> datoteka.readline()
'Venera\n'
>>> datoteka.readline()
'Zemlja\n'
>>> datoteka.readlines()
['Mars\n', 'Jupiter\n', 'Saturn\n', 'Uran\n', 'Neptun\n', 'Pluton\n']

Najprej smo prebrali tri vrstice in nato ostale. Vračanja nazaj ni. (Je, ampak ... pustimo.) In ko smo na koncu, smo na koncu.

>>> datoteka.readline()
''
>>> datoteka.readlines()
[]

Spodobi se, da povemo za še eno metodo: close. Z njo datoteko zapremo. Zapiranje je potrebno zato, ker operacijskemu sistemu (da, ne Pythonu, temveč Windowsom oz. Os Xu oz. Linuxu) s tem povemo, da te datoteke ne beremo več. To je pomembno predvsem zato, ker bi se kak drug program morda odločil v datoteko pisati, vendar mu sistem tega ne bo pustil.

V Pythonu zapiranje datoteke ni povsem nujno. Če datoteko odpremo v funkciji, se bo kar sama od sebe zaprla, ko bo funkcije konec. Pravijo, da na to ni lepo računati, vendar vsaj sam to pogosto počnem. Datoteko rad odprem kar v zanki for, takole

for vrstice in open("planeti.txt"):

Saj res ni potrebe, da bi jo tlačili v kakšno posebno spremenljivko.

To je pomembno predvsem zato, ker bi se kak drug program morda odločil v datoteko pisati... Pa znamo mi pisati v datoteko?

Pisanje datotek

Funkcija open sprejme poleg imena datoteke, ki jo želimo odpreti, še nekaj argumentov. Drugi po vrsti vsebuje način, na katerega želimo odpreti datoteko. Ta je lahko "r", "w" ali "a". Način "r" smo že videli zgoraj in vemo, da se nanaša na branje. Potem je očitno, da se "w" na pisanje. Način "a" pa pomeni dodajanje na konec datoteke "a".

Če torej napišemo

>>> datoteka = open("bogovi.txt", "w")

ustvarimo novo datoteko z imenom bogovi.txt. Če je takšna datoteka že obstajala ... tough luck. Zdaj obstaja prazna datoteka s tem imenom. Da uganemo, kako se imenuje metoda za pisanje v datoteko, ni potrebno biti Einstein.

>>> datoteka.write("Hermes")
6

6?! Zakaj je metoda write vrnila 6? Povedala je, koliko znakov je napisala v datoteko. Zakaj? Pojma nimam.

>>> datoteka.write("Afrodita")
8
>>> datoteka.write("Ares")
4
>>> datoteka.write("Zeus")
4
>>> datoteka.write("Kronos")
6

Pri vseh teh vrnjenih številkah je smešno to, da Python v datoteko v resnici (najbrž) še ni napisal ničesar. Če odpremo datoteko, bomo najbrž odkrili, da je prazna. V resnici ne piše vsake drobnarije posebej, temveč čaka, da se bo malo nabralo. Zagotovo pa bo zapisal, vse kar je treba, če datoteko zapremo.

>>> datoteka.close()

Po tem ne moremo več pisati vanjo.

>>> datoteka.write("Caelus")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

Pač pa lahko datoteko odpremo s PyCharmom ali čim podobnim.

HermesAfroditaAresZeusKronosKronos

Ups. Ja, write ni print (hvalabogu). write ne dodaja praznih vrstic.

>>> datoteka = open("bogovi.txt", "w")
>>> datoteka.write("Hermes\n")
7
>>> datoteka.write("Afrodita\n")
9
>>> datoteka.write("Ares\n")
5
>>> datoteka.close()

Če zdaj odpremo datoteko, dobimo, kar smo (najbrž) želeli:

Hermes
Afrodita
Ares

Metoda write se od printa razlikuje še po nečem: medtem ko print dobi poljubno število argumentov poljubnega tipa, sprejme write kot argument en in natančno en niz. Če bi hoteli izpisati prvih deset števil in njihove kvadrate, bi pisali

>>> for i in range(10):
...     print(i, i ** 2)

Če bi jih hoteli zapisati v datoteko, bi jih morali najprej preoblikovati v nize.

>>> k = open("kvadrati.txt")
>>> for i in range(10):
>>>     k.write(f"{i} {i ** 2}")
>>> k.close()

Trenutni direktorij

Kje mora biti datoteka, da jo bo open našel? Ali, kadar pišemo, kam, v kateri direktorij, jo bo shranil?

Vsak program, ki trenutno teče, ima določen "trenutni direktoij". V začetku mu ga na nek način določi operacijski sistem (kako, je odvisno od sistema, programa in bogvečesa še). V Pythonu ga izvemo s funkcijo os.getcwd().

>>> import os
>>> os.getcwd()
'/Users/janezdemsar/Dropbox/'

Kratica "cwd" pomeni "current working directory". Spremenimo ga z os.chdir():

os.chdir("/Users/jananovak/Desktop")

Če po tem odpremo datoteko v načinu "w", se bo le-ta pojavila v direktoriju /Users/jananovak/Desktop, kar bi znalo biti Janino namizje.

Ime datoteke je lahko tudi relativno ali absolutno. Če je trenutni direktorij /Users/jananovak/Desktop in napišemo open("fakulteta/naloge/maraton.py"), bo Python poskusil odpreti datoteko "/Users/jananovak/Desktop/fakulteta/naloge/maraton.py". Takšno ime je torej relativno glede na trenutni direktorij.

Če pa ime datoteke začnemo s poševnico, gre za absolutno ime, od, s kleno slovensko besedo, korenskega imenika oziroma mape. Napišemo lahko torej, na primer, open("/Users/janezdemsar/Dropbox/planeti.txt").

Ob poševnicah povejmo še, da dandanes vsi operacijski sistemi sprejemajo običajno, "napredno" poševnico, /. Windowsi (oziroma MS-DOS, na katerem temeljijo) so dolgo uporabljali vzvratno poševnico \, ki pa povzroča same sitnosti; med drugim to, da "c:\Users\Peter\namizje" ni to, kar se zdi, temveč

c:\Users\Peter
amizje

Če res morate uporabljati vzvratno poševnico, boste morali, kot smo se pred čisto kratkim učili, pisati po dve skupaj, da dobite eno, "c:\\Users\\Peter\\namizje".

Kako je zapisano besedilo

Računalnik v resnici shranjuje samo številke. Da lahko shranimo besedilo, se zmenimo, da vsak znak predstavimo z določeno številko. V ta namen je bilo razvitih več standardov, od katerih je preživel bolj ali manj samo ASCII. Ta pravi, recimo, da veliki A predstavimo s številko 65, B s 66, C z 67... Mali a je 97, b 98 ... in tako naprej. Svoje kode imajo tudi drugi znaki: presledek je 32, pika je 46, oklepaj 40... Vsak znak, ki ga lahko natipkamo ali kako drugače napišemo, mora imeti neko kodo. Problem: ASCII je (bil) sedembitni standard, pri čemer so prvih 32 znakov zasedle kontrolne kode, kot recimo prehod v novo vrsto, tabulator, zvonček in še kup eksotičnih reči, ki so jih potrebovali za teleprinterje. Tako so proste le kode od 32 do 127, s čimer lahko zapišemo 96 znakov. To nekako zadošča za angleško abecedo, ločila in še par malenkosti, za trde in mehke č-je pa se Američani, ki so sestavljali standard, niso posebej menili. Kitajci pa si niti s celotnim naborom 96 mest ne bi mogli veliko pomagati.

ASCII je doživel kup razširitev. Že v sedembitni različici smo ga v rajnki domovini popravili v YUSCII, v katerem smo žrtvovali nekaj znakov, kot so zaviti oklepaji in vzvratne poševnice in jih zamenjali s šumniki. Microsoft in IBM sta kasneje sestavila vsak svoj standard, takoimenovane kodne tabele; gre za razporede, v katerih je "spodnji del", znaki s kodami do 127, enak kot v izvirnem ASCII, v gornjem delu - kode od 128 do 256 - pa so različni drugi znaki. V naših krajih se je najbolj prijela tabela CP1250, namenjeni srednje- in vzhodnoevropskim jezikom, v kateri ima, recimo, č kodo 232. V zahodnoevroski tabeli, CP1252, č-ja ni, na mestu 232 pa je znak è. Poleg teh, Microsoftovih tabel, obstajajo IBMove, kjer je Slovanom namenjen IBM 852; da bi bilo programiranje še preprostejše, obstaja tudi tretji standard, ISO-8859, ki nam je namenil tabelo ISO-8859-2, ki je podobna, vendar ne povsem enaka CP1250.

Ko torej računalnik prikazuje kako besedilo, mora vedeti, v katerem kodnem naboru je zapisan, se pravi, kako razumeti, recimo, številko 232. Če je besedilo v CP1252, je to è, če v CP1250, pa č. In kako naj to ve? V splošnem ne more! Spletne strani navadno vsebujejo glavo, v kateri je zapisan podatek o uporabljenem kodnem naboru. Če tega ni, se bodo šumniki pokazali prav ali pa tudi ne. In še huje: vse besedilo je napisano v istem kodnem naboru, zato se lahko zgodi, da ne bo moglo vsebovati tako črke č kot è.

Obenem pa s tem Kitajci še vedno ne morejo biti zadovoljni, saj jim dodatnih 128 znakov bore malo pomaga.

Takole smo se hecali do pred nekaj leti, ko je prišel v širšo rabo standard Unicode. Ta je narejen takole: vsakemu znaku je prirejena ena koda. Kod je 232, kar je čez in čez dovolj tudi za Kitajce (vsak ima lahko svojo, če hoče!). Nekatere znake je mogoče pisati tudi na več načinov: obstaja, recimo, znak za strešico in č lahko zapišemo kot č (koda 269) ali kot strešico (309) in c (99). Da bi lahko zapisal 232 kod, bi potrebovali po štiri bajte na znak. Američanov to ne bi posebej osrečilo, saj bi se dolžina njihovih besedil početverila in bi se spraševali, kaj je bilo pravzaprav narobe s starim dobrim ASCII. Zato so se snovalci standarda domislili različnih načinov, kako "stisniti" zapis, da bo zahteval manj kot štiri bajte na znak. Med njimi je daleč najbolj razširjen UTF-8, občasno pa vidimo še UTF-16. UTF-8 je sestavljen zelo zvito: če imamo besedilo v starem dobrem ASCIIju (brez neangleških znakov, torej brez kakšnih šumnikov ali francoskih naglasov), se lahko delamo, da je v UTF-8. Čim vsebuje šumnike (recimo po standardu CP1250), pa ni več združljiv z UTF-8 in moramo, če ga hočemo pravilno prebrati, vedeti, po katerem standardu je kodirano.

Smo prišli z dežja pod kap? Smo imeli prej polno kodnih naborov (CP1250 do CP125bogvekoliko, pa IBMovi nabori, pa ISO-8859), zdaj pa imamo kup UTFjev? Niti ne. Razlika je v tem, da so stari nabori določali, kakšne kode imajo posamezni znaki. Po novem imajo vsi znaki določene kode, obstaja le par načinov zapisa teh kod. Med njimi pa vsaj na zahodu prevladuje UTF-8.

Če imamo torej neko besedilo - recimo v neki datoteki, ali pa na neki spletni strani -, ki ga želimo prebrati, bo ponavadi zapisano v UTF-8 ali CP1250. Če je angleško in vsebuje le angleške znake, je to eno in isto. Če gre za slovensko besedilo, pa moramo vedeti, kako je zapisano. Če gre za novejše besedilo, je verjetno v UTF-8, če je starejše kot kakih deset let ali pa prihaja iz vira, s katerim ima kaj opraviti kakšen domači amaterskih izdelovalec spletnih strani, pa CP-1250. CP-1250 se žal še vedno kar pogosto pojavlja tudi v bližini Windowsov. Vedno ga lahko tudi poskusimo najprej prebrati kot UTF-8; če to uspe, je najbrž v resnici UTF-8, sicer ga preberemo kot CP1250.

Kako Pythonu povemo, kako naj bere besedilo? Metoda open ima še par argumentov. Tretji nas ne zanima in naj ima vrednost -1, četrti pa poda vrsto zapisa.

open("planeti.txt", "r", -1, "utf8")

Enkrat bomo izvedeli, da je mogoče argumente funkcij podajati tudi malo drugače in ugotovili, da je open primerneje poklicati z

open("planeti.txt", "r", encoding="utf8")

Pa če Pythonu ne povemo, kot kaj želimo brati besedilo - kot kaj ga bo poskusil brati? To je odvisno od nastavitev sistema - zato imajo Windowsi nastavitev "System locale" (v Windowsih 7 jo boste našli v Region and Language). Tam sprašuje po "Current language for non-Unicode programs"; če imate nastavljeno slovenščino, bo privzeti kodni nabor CP1250. Na Ubuntuju in Macu je kot privzeto kodiranje nastavljen UTF-8. Kako je s tem pri vas, vam pove funkcija locale.getpreferrencoding().

Kaj pri branju in pisanju datotek pravzaprav naredi ta nastavitev? Tole: ko zapišemo nek niz v datoteko, ga mora Python pretvoriti v zaporedje številk. Ta nastavitev pove, kako - v katero številko naj se spremeni posamezni znak. (Pozoren študent, ki kaj ve, ve tudi, da so tudi nizi v Pythonu, torej spremenljivke tipa str, že shranjene kot zaporedje številke. Drži, vendar Python interno shranjuje nize pač v nekem standardu in nič nas ne briga, v katerem (v resnici v UTF-16 ali UTF-32, saj bi bil UTF-8 za tole zelo nepraktičen), pri zapisovanju v datoteko pa pretvori v tak standard, kot želimo. Pri branju pa je ravno obratno: ko Python bere datoteko, mora spreminjati številke v znake niza; ta nastavitev pove, kako.

Binarne datoteke - in nizi bajtov

Obstajajo datoteke, ki niso besedilne. Očiten primer so datoteke, ki shranjujejo slike, zvok ali filme. Manj očiten primer je datoteka z Wordovim dokumentom. Ta sicer vsebuje besedilo, vendar ni opisano tako, da bi bilo v njej le besedilo, lepo od prvega do zadnjega znaka (96 je mali a in tako naprej...), temveč vsebuje še kup dodatnih reči. (Da ne govorimo o tem, da je v novejših različicah še zazipano, kar lahko preverite tako, da ga preimenujete iz .docx v .zip in odzipate.) Vsebine teh datotek torej ne moremo brati kot besedilo - torej kot številke, ki pomenijo znake - temveč le kot številke.

Odprimo datoteko .gif in jo preberimo v niz ter izpišimo prvih 30 znakov.

>>> gif = open("FRI.gif")
>>> s = gif.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/janezdemsar/env/o3/bin/../lib/python3.4/codecs.py", line 313, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 6: invalid continuation byte

Na tem mestu se spomnim, da še nisem povedal, kaj se sploh zgodi, če poskusimo datoteko odpreti z napačnim načinom kodiranja. Mesto je kar primerno, saj lahko rečem preprosto: no, tole. 'utf-8' codec can't decode...

Tega sicer ne bo znal "dekodirati" noben "kodek", ker "kodeki" iz številk razberejo znake, tole pa pač ni besedilna datoteka in se je ne da z nobenim kodekom spremeniti v kaj podobnega besedilu.

Ko odpremo datoteko, moramo povedati, da ne gre za besedilno temveč "številsko" ali, bolj učeno, binarno datoteko.

>>> gif = open("wafl.gif", "rb")
>>> s = gif.read()
>>> s[:30]
b'GIF89a\xe9\x00\xc7\x00\xf7\x00\x00\xe5\x93\x90\xe0|y\x8c\x8b\x8a\xe2\x86\x83\xd6\xd6\xd5\xf9\xe7'

Da gre za binarno datoteko, smo povedali tako, da smo kot drugi argument podali "rb": r za branje in b za binarno. Tako kot prej smo z read() prebrali celotno datoteko, le da smo zdaj previdno izpisali le prvih 30 znakov.

Kar smo dobili, je videti kot niz; začne se s črkami GIF89a, sledijo pa znaki z nekimi čudnimi kodami; ker so izven običajnega obsega ASCII (32-127), je Python izpisal njihove kode. Prvi trije za GIF89a imajo kot 233, 0 in 199, zato jih je, po šestnajstiško, izpisal kot \x01, \x04 in \xe5.

V resnici pa ne gre za čisto pravi niz - sumljiv je tisti b pred začetnim narekovajem. Gre za nov podatkovni tip: imenuje se bytes. Navzven je zelo zelo podoben nizu; ne le, da se podobno izpisuje, temveč ima tudi običajne metode nizov, kot so strip, find in join. Razlikuje se v par podrobnostih.

Če želimo dobiti prvi, tretji, osemnajsti... znak tega "niza", ne dobimo črke, temveč številko.

>>> s[0]
71
>>> s[1]
73
>>> s[2]
70
>>> s[6]
233
>>> s[8]
199

Reči tipa bytes so torej križanec med seznami in nizi: navzven so videti kot nizi, navznoter pa so - kot nam razodene indeksiranje - pravzaprav seznami 8-bitni števil, torej števil med 0 in 255.

Ker so s[0], s[1] in s[2] številke 71, 73 in 70, ki (po ASCII) ustrezajo znakom G, I in F, jih je Python izpisal kot G, I in F. Tisto, kar ni podobno ničemur, je izpisal z onimi \x.

Mimogrede, gif, ki smo ga naložili, je velik 233x199 točk. Kar je slučajno ravno vsebina s[6] in s[8]. (Sem rekel, da so bytes številke med 0 in 255? GIF pa je lahko tudi večji. Kako je shranjena velikost gifov, večjih od 255 točk, je velika skrivnost, ki jo znajo razriti samo tisti, ki znajo uporabljati Google.)

O branju binarnih datotek nimamo povedati kaj prida več. Razen tega, da se je po njih smiselno sprehajati: z metodo seek lahko skočimo na poljubno mesto v datoteki, metoda tell pa pove, kje v datoteki smo.

Pretvarjanje med str in bytes

Vzemimo niz

>>> s = "Demšar"
>>> len(s)
6

Če bi ga želeli zapisati v datoteko, bi morali ob odpiranju datoteke povedati, na kakšen način naj ga zakodira (predvsem zaradi š-ja) ali pa pustiti operacijskemu sistemu, da se odloči. "Zakodirati" tu pomeni spremeniti v številke.

Včasih pa želimo to pretvorbo opraviti sami. Iz niza s bomo naredili zaporedje številk, skladno z določenim kodekom. Temu rečemo kodiranje, opravi pa ga metoda encode, ki ji kot argument podamo kodek.

>>> s = "Večna pot"
>>> len(s)
9
>>> b = s.encode("cp1250")
>>> b
b'Ve\xe8na pot'
>>> len(b)
9
>>> b[0]
86
>>> b[2]
232

Še enkrat (tole ni zapleteno, se pa zna zazdeti takšno, če ne bomo pozorno spremljali): nizi (str) imajo metodo encode, s katero jih spremenimo v zaporedje števil (bytes). V gornjem primeru smo iz šestih znakov dobili šest številk. Prva je 86, saj je to koda (ki v cp1250, pa tudi v ASCII) pripada veliki črki V. Druga številka je 232 (\xe8), saj le-ta pripada malemu č - v kodeku cp1250.

Če bi poskusili s kakim drugim kodekom, recimo "cp1252", se to ne bi obneslo.

>>> c = s.encode("cp1252")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/janezdemsar/env/o3/bin/../lib/python3.4/encodings/cp1252.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_table)
UnicodeEncodeError: 'charmap' codec can't encode character '\u010d' in position 2: character maps to <undefined>

Črke "č" v tem kodnem razporedu ni.

Zdaj pa obrnimo. Imamo zaporedje bajtov, b. Konkretno, imamo zaporedje števil

>>> list(b)
[86, 101, 232, 110, 97, 32, 112, 111, 116]

Če želimo spremeniti b v niz, v besedilo, moramo te črke "dekodirati". Ob tem moramo povedati, s kakšnim kodekom. Vemo: cp1250.

>>> b.decode("cp1250")
'Večna pot'

Pa če zgrešimo? Če namesto cp1250 uporabimo cp1252? Tokrat bo delovalo. V bo V in e bo e, saj sta tadva znaka v vseh kodekih na istem mestu (lepo je biti Američan). Kot tretji znak pa bomo namesto č dobili pač tisti znak, ki ima v izbranem kodeku kodo 232.

>>> b.decode("cp1252")
'Veèna pot'

Znano? Ste že kdaj videli è namesto č? Recimo v podnapisih v VLC? No, zdaj veste, zakaj: zato, ker je nek program dobil besedilo, zapisano v cp1250, mislil pa je, da je v cp1252, zato je kodo 232 prebral kot è in ne kot č.

Kako pa deluje ta stran? V katerem kodeku je zapisana, da ima tako è-je kot č-je? V UTF-8, v katerem je lahko zapisano vse.

>>> b = s.encode("utf-8")
>>> len(s)
9
>>> len(b)
10

Čeprav je besedilo dolgo 9 znakov, je zakodirano z 10 števili.

>>> b
b'Ve\xc4\x8dna pot'
>>> b[2]
196
>>> b[3]
141

Znak č se zapiše s zaporedjem 196, 141. Če bi malo pobrskali, bi videli, da se è zapiše kot 195, 168. V pa se zapiše z eno samo številko, 86, tako kot prej. (Lepo je biti Američan.)

Dekodiranje je seveda takšno kot prej, le z drugim kodekom.

>>> b.decode("utf-8")
'Večna pot'

Omenimo še en kodek, ki ga vsakodnevno uporabljate: utf-16.

>>> s
'Večna pot'
>>> b = s.encode("utf-16-le")
>>> b
b'V\x00e\x00\r\x01n\x00a\x00 \x00p\x00o\x00t\x00'
>>> len(b)
18

Iz 9 znakov je naredil 18 številk, pri čemer je prva številka vedno enaka koti ASCII (v izpisanem zaporedju lahko preberete V, e, n, a, presledek, p, o in t, druga pa je 0 (\x00). Izjema je "č", ki ga je zapisal z zaporedjem 13, 1.

Kdaj to vsakodnevno uporabljate? Ko pišete SMSe. Če v njem ni šumnikov, ga zakodira kot ASCII, če so, pa kot UTF-16. Ker ta za vsak znak porabi dve številki, so SMSi s šumniki dvakrat daljši - v en SMS gre samo 72 znakov.

Zadnja sprememba: sobota, 6. oktober 2018, 22:24
Zadnja sprememba: torek, 12 marec 2019, 10:00 AM