2. Rad s datotekama

U ovom poglavlju opisan je rad s tekstovnim datotekama. Tekstovne datoteke (Text file) su one datoteke koje sadrže strojno čitljive znakove. Znak može biti bilo koje slovo abecede (npr. A, č, é), znamenka (npr. 1, 4, 0) i drugi ne-alfanumerički znakovi (npr. !, #, €). Znakovi također mogu biti i drugi neobični simboli (npr. slova ne-latinične abecede, kineski znakovi, razni tehnički simboli, simboli fonetskih zapisa). Osim tekstovnih datoteka, postoje tzv. binarne datoteke (Binary file) koje ne sadrže strojno čitljive znakove. Takve datoteke su npr. slikovni, zvučni ili video zapisi. Ovo poglavlje bavi se samo radom nad tekstovnim datotekama.

2.1. Otvaranje i čitanje datoteke

Prije čitanja iz datoteke ili pisanja u datoteku, potrebno je datoteku otvoriti funkcijom open(), koja služi kao pristup datoteci. Kada se datoteka otvori, određuje se što će se dalje s tom datotekom raditi, odnosno na koji način će joj se pristupiti. Sintaksa ove funkcije je open(file_name [, access_mode][, buffering]). Prvi argument file_name obavezan je argument koji sadrži naziv datoteke koju otvaramo odnosno kojom pristupamo. Drugi argument access_mode opcionalni je element koji sadrži način pristupa datoteci, odnosno što ćemo s tom datotekom raditi. Postoji više vrijednosti koje drugi argument može poprimiti, ali za potrebe ovog priručnika, opisat ćemo samo dvije: r i w. Sve vrijednosti drugog argumenta nalaze se u navodnicima. Vrijednost r znači da otvorenu datoteku želimo pročitati (od engleske riječi read) te je pretpostavljena vrijednost u slučaju kada nije ništa navedeno. Vrijednost w znači da u otvorenu datoteku želimo pisati (od engleske riječi write). Ako datoteka s nazivom file_nameveć postoji, ovim argumentom ćemo obrisati postojeće podatke i zapisati nove. U slučaju ako datoteka s nazivom file_name ne postoji, stvorit će se nova datoteka s tim nazivom. Treći argument buffering je također opcionalni element koji sadrži podatak o privremenoj pohrani podataka. Taj argument nećemo upotrebljavati, tako da će pretpostavljena vrijednost biti definirana od strane sustava.

U sljedećem primjeru postojeću datoteku tekst.txt želimo otvoriti, pročitati koristeći metodu read() nad otvorenom datotekom, sadržaj pridružiti varijabli f te ga ispisati. Metoda read() čita postojeću datoteku od početka do kraja te je vraća kao niz znakova.


In [124]:
f=open('tekst.txt','r').read()
print f


Prvi red.
Drugi red.
Treći red.

Budući da je vrijednost r drugog argumenta opcionalna i pretpostavljena vrijednost, vrijednost možemo izostaviti i dobiti jednaku izlaznu vrijednost:


In [125]:
f=open('tekst.txt').read()
print f


Prvi red.
Drugi red.
Treći red.

2.2. Pisanje datoteke

Kada želimo nešto zapisati u datoteku, potrebno je datoteku otvoriti funkcijom open(). Prvi argument sadrži naziv datoteke u koju ćemo zapisati željeni sadržaj. Drugi argument sadrži vrijednost w. Ako već postoji datoteka s nazivom iz prvog argumenta, postojeći podaci će biti obrisani, a novi će biti zapisani. U slučaju ako datoteka s nazivom iz prvog argumenta ne postoji, stvorit će se nova datoteka s tim nazivom. To ćemo pridružiti varijabli npr. f.

Zatim ćemo nad varijablom f pokretati metodu write(). Metoda write() prima kao argument niz znakova koji želimo zapisati u datoteku. Možemo pokrenuti više metoda write() uzastopno.

Konačno, nakon što završimo zapisivanje u datoteku, obavezno trebamo datoteku i zatvoriti metodom close(). Time dajemo uputu da više nećemo pristupati datoteci i u nju zapisivati. Nakon metode close() nije više moguće pisanje u datoteku.

Na sljedeći način ćemo pohraniti niz znakova u novu datoteku datoteka.txt:


In [126]:
f=open('datoteka.txt','w')
f.write('Ovo je prvi red.')
f.write('Je li ovo drugi red?\nOvo je 2. red.')
f.write('\nOvo je 3. red.')
f.close()

Pročitat ćemo datoteku datoteka.txt kako bismo provjerili jesmo li uspješno zapisali niz znakova.


In [127]:
f=open('datoteka.txt').read()
print f


Ovo je prvi red.Je li ovo drugi red?
Ovo je 2. red.
Ovo je 3. red.

Iz primjera je vidljivo da metoda write()ne dodaje novi red na kraju niza, stoga ga je potrebno izričito zapisati posebnim znakom \n.

Osim čitanjem novostvorene datoteke, provjeriti jesmo li uspješno izvršili pisanje u datoteku možemo tako da pogledamo nalazi li se datoteka na našem računalu, a otvaranjem u nekom editoru teksta provjeriti njen sadržaj. Lokacija novostvorene datoteke na računalu je isti direktorij iz kojeg pokrećemo Python program.

Datoteka će se najvjerojatnije nalaziti u sljedećim direktorijima:

  • u Windows operacijskom sustavu: C:\Python27\
  • u Mac operacijskom sustavu: Users\Korisnik\.

In [128]:
f=open('datoteka.txt').read()
print f


Ovo je prvi red.Je li ovo drugi red?
Ovo je 2. red.
Ovo je 3. red.

2.3. Iteriranje kroz datoteku

Do sada je obrađena iteracija kroz niz znakova i listu. Niz znakova možemo opisati kao sekvencu znakova, pa se iteracija kroz niz znakova vrši znak po znak. Listu možemo opisati kao sekvencu elemenata liste, pa se iteracija kroz listu vrši od prvog elementa liste do posljednjeg. Tekstovnu datoteku također možemo opisati kao sekvencu redova. Iteracija kroz tekstovnu datoteku vrši se red po red.


In [129]:
for red in open('tekst.txt'):
    print 'Red datoteke: ' + red


Red datoteke: Prvi red.

Red datoteke: Drugi red.

Red datoteke: Treći red.

U for petlji pomoćna varijabla red poprima sadržaj reda datoteke, a kao tip podatka je niz znakova. Možemo provjeriti pomoću funkcije type():


In [130]:
for red in open('tekst.txt'):
    print 'Red datoteke je sljedeći tip podatka: ' + str(type(red))


Red datoteke je sljedeći tip podatka: <type 'str'>
Red datoteke je sljedeći tip podatka: <type 'str'>
Red datoteke je sljedeći tip podatka: <type 'str'>

Iz primjera je vidljivo da su sva tri reda datoteke nizovi znakova.

Broj redova datoteke možemo izračunati pomoću brojača, koji prvo moramo svesti na vrijednost 0:


In [131]:
broj_redova=0
for red in open('tekst.txt'):
    broj_redova+=1
print broj_redova


3

U sljedećem primjeru želimo ispisati samo onaj red koji počinje s velikim slovom D. Prvo ćemo iterirati kroz datoteku red po red. Zatim ćemo postaviti logičku uvjet if naredbom koji provjerava je li prvi znak trenutnog reda 'D'.


In [132]:
for red in open('tekst.txt'):
    if red[0]=='D':
        print red


Drugi red.

U slučaju da stavimo u logički uvjet znak 'd', a ne 'D', nećemo dobiti nikakav ispis jer uvjet nije ispunjen.


In [133]:
for red in open('tekst.txt'):
    if red[0]=='d':
        print red

Međutim, ako pomoćnu varijablu red pretvorimo u mala slova metodom lower(), uvjet će biti ispunjen:


In [134]:
for red in open('tekst.txt'):
    if red.lower()[0]=='d':
        print red


Drugi red.

Prvo smo iterirali kroz datoteku. U svakoj iteraciji pomoćna varijabla red poprimi sadržaj reda datoteke. U if naredbi postavljamo logički uvjet tako da prvo sva slova u pomoćnoj varijabli red pretvorimo u mala slova metodom lower() (red.lower()). Zatim indeksiramo prvi znak u redu ([0]) te uspoređujemo je li taj znak identičan znaku 'd'.

Na sljedeći način možemo ispisati samo one redove u kojima je treći znak samoglasnik:


In [135]:
for red in open('tekst.txt'):
    if red.lower()[2] in 'aeiou':
        print red


Drugi red.

Treći red.

Nad datotekom koju smo stvorili (datoteka.txt) možemo izračunati i ispisati dužinu svakog reda:


In [136]:
for red in open('datoteka.txt'):
    print red
    print 'Dužina reda: ' + str(len(red))


Ovo je prvi red.Je li ovo drugi red?

Dužina reda: 37
Ovo je 2. red.

Dužina reda: 15
Ovo je 3. red.
Dužina reda: 14

Iteriramo kroz datoteku datoteka.txt. Zatim ispisujemo sadržaj pomoćne varijable red. Nakon toga ispisujemo dužinu reda pomoću funkcije len(), koju je potrebno pretvoriti iz cjelobrojne vrijednosti u niz znakova pomoću funkcije str(). Iz primjera je vidljivo da u je u prvom i drugom redu dužina reda povećana za 1, a u trećem nije. Potrebno je zapamtiti da posebni znakovi također ulaze u dužinu reda. U ovom slučaju u prvom i drugom redu uračunat je znak \n koji se nalazi na kraju redova. Budući da treći odnosno posljednji red nema prelazak u novi red, nema ni znaka \n.

Osim iteriranja red po red, kroz datoteku je moguće iterirati znak po znak, tako da se u iterira kroz datoteku nad kojom je izvršena metoda read().


In [137]:
for element in open('datoteka.txt').read():
    print element


O
v
o
 
j
e
 
p
r
v
i
 
r
e
d
.
J
e
 
l
i
 
o
v
o
 
d
r
u
g
i
 
r
e
d
?


O
v
o
 
j
e
 
2
.
 
r
e
d
.


O
v
o
 
j
e
 
3
.
 
r
e
d
.

U sljedećem primjeru možemo izbrojati koliko ima samoglasnika u datoteci datoteka.txt. Nakon metode read(), pokrenut ćemo metodu lower() kako bismo pretvorili sva slova u mala slova:


In [138]:
broj_samoglasnika=0
for element in open('datoteka.txt').read().lower():
    if element in 'aeiou':
        broj_samoglasnika+=1
print broj_samoglasnika


20

2.4. Kodiranje

2.4.1. Unicode standard

Unicode (Unicode, Unicode) osigurava jedinstveni broj za svaki znak, neovisno o platformi, programu i jeziku. Unicode je međunarodni standard izdan prvi put 1991. godine (Unicode 1.0) od strane Unicode konzorcija, koji je zadužen za održavanje standarda. Posljednja verzija Unicode 9.0 izdana je u lipnju 2016. godine. Sadrži više od 128.000 znakova iz 135 pisama. Samo za usporedbu, verzija Unicode 7.0 izdana u lipnju 2014. godine sadržavala je više od 113.000 znakova iz 123 pisama, verzija Unicode 5.0 izdana u srpnju 2006. godine sadržavala je 99.000 znakova iz 64 pisama, dok je prva verzija Unicode 1.0 izdana u listopadu 1991. sadržavala je nešto više od 7.000 znakova iz 24 pisama.

Kodne stranice (Code page) su tablice jedinstvenih brojeva kojim se zapisuje određeni skup znakova. Unicode je jedan kodna stranica čiji skup sadrži više od 128.000 znakova. Međutim, postoje i druge kodne stranice. Npr. u Windows okruženju postoji ISO-8859-2 kodna stranica koja definira kodiranje i hrvatskih slova ili ISO-8859-5 kodna stranica koja definira kodiranje srpskih ćiriličnih slova.

Prije nastanka Unicodea, postojalo je više kodnih stranica, ali niti jednog standarda. To je izazivalo mnogo problema, jer jedinstveni broj za određeni znak u jednoj kodnoj stranici je u drugoj kodnoj stranici možda predstavljao neki drugi znak. Drugim riječima, dvije kodne stranice koriste isti broj za kodiranje dva različita znaka ili koriste različite brojeve za kodiranje istog znaka. To je i danas vidljivo kod slanja e-pošte kada su slova s dijakritičkim znakovima predstavljena nekim drugim slovima, znakovima ili njihovim kombinacijama. Kako ne bi dolazilo do pogreške u prijenosu podataka, uvođenje standarda za kodiranje znakova bilo je neophodno. Standard definira kojim brojem će biti kodiran koji znak, tako da kodiranje bude dosljedno.

Unicode standard organiziran je tako da se kodne vrijednosti svakog znaka pišu u heksadecimalnom sustavu. Na određeni znak upućuje se tako da se ispred heksadecimalne vrijednosti znaka piše "U+". Na primjer, razmak (" ") ima vrijednost U+0020 i nosi ime "SPACE", uskličnik ("!") ima vrijednost U+0021 i nosi ime "EXCLAMATION MARK", veliko slovo "A" ima vrijednost U+0041 i nosi ime "LATIN CAPITAL LETTER A", dok malo slovo "a" ima vrijednost U+0061 i nosi ime "LATIN SMALL LETTER A". Znakovi su organizirani u 17 ravnina (Plane (Unicode)) od 0 do 16 u decimalnom zapisu, odnosno od 0 do 10 u heksadecimalnom zapisu. Prva i osnovna višejezična ploča je BMP (basic multilingual pane), ima vrijednost 0 te sadrži znakove koji se najviše upotrebljavaju. Ostale ravnine od 1 do 16 su dopunske razine.

Unicode se može implementirati na više načina. Najčešći način implementacije Unicodea je UTF-8. Kratica UTF stoji za Unicode Transformation Format, a brojka govori koliki bitova sadrži najkraći zapis znaka. UTF-8 koristi 8 bitova ili jedan bajt za najkraći zapis znaka. Jednim bajtom zapisano je prvih 128 znakova iz ASCII kodne stranice (ASCII). Za prikaz slova s dijakritičkim znakovima potrebna su dva bajta. UTF-8 koristi četiri bajta za najduži zapis znaka. UTF-8 je najčešći način kodiranja znakova. U prosincu 2016. čak preko 88% mrežnih stranica koristi UTF-8 kodiranje (Usage of character encodings for websites).

2.4.2. Unicode tip podataka

Osim niz znakova kao tip podataka, Python poznaje i Unicode tip podataka. Taj tip podataka treba koristiti kada se koriste "ne-ASCII" znakovi, kao što su slova s dijakritičkim znakovima ili ne-latinične abecede. Funkcije i metode koje se pokreću nad nizovima znakova moguće je pokretati i nad tipom podataka Unicode.

Problem prikaza slova s dijakritičkim znakovima vidljiv je u sljedećem primjeru:


In [139]:
'šećer'


Out[139]:
'\xc5\xa1e\xc4\x87er'

Ako izračunamo dužinu niza šećer, primijetit ćemo da je dužina 7, a ne 5.


In [140]:
len('šećer')


Out[140]:
7

Slovo "š" i slovo "ć" zapisani su pomoću dva znaka, umjesto da su zapisani jednim znakom. Zato je potrebno niz znakova pravilno dekodirati iz tipa podataka string u tip podataka Unicode.

2.4.2.1. Metoda decode()

Metoda decode() dekodira niz znakova koristeći navedenu kodnu stranicu. Kao argument metoda prima naziv kodne stranice (npr. utf8 ili iso-8859-2). Ova metoda koristi se pri čitanju iz datoteke.

Iz prethodnog primjera možemo pravilno dekodirati niz znakova 'šećer':


In [141]:
'šećer'.decode('utf8')


Out[141]:
u'\u0161e\u0107er'

U primjeru vidimo da vrijednost zapisa slova "š" iznosi \u0161, a vrijednost zapisa slova "ć" iznosi \u0107. Kada izračunamo dužinu zadanog niza, vidjet ćemo da iznosi 5 (odnosno koliko i ima znakova u nizu), a ne 7, kao što je iznosilo prije dekodiranja (odnosno kada su slova s dijakritičkim znakovima zapisana pomoću dva znaka, umjesto jednim znakom).


In [142]:
len('šećer'.decode('utf8'))


Out[142]:
5

In [143]:
len('šećer')==len('šećer'.decode('utf8'))


Out[143]:
False

Isti problem možemo primijetiti kod datoteka. U sljedećem primjeru ispisujemo znak po znak iz datoteke tekst.txt:


In [144]:
for znak in open('tekst.txt').read():
    print znak


P
r
v
i
 
r
e
d
.


D
r
u
g
i
 
r
e
d
.


T
r
e
�
�
i
 
r
e
d
.

Možemo primijetiti da je slovo "ć" prikazano pomoću dva neidentificirana znaka. Ako nakon čitanja datoteke odredimo koju kodnu stranicu treba koristiti za dekodiranje, slovo "ć" će biti ispravno prikazano.


In [145]:
for znak in open('tekst.txt').read().decode('utf8'):
    print znak


P
r
v
i
 
r
e
d
.


D
r
u
g
i
 
r
e
d
.


T
r
e
ć
i
 
r
e
d
.

Problem možemo primijetiti i kod računanja duljine datoteke:


In [146]:
print len(open('tekst.txt').read())
print len(open('tekst.txt').read().decode('utf8'))
print len(open('tekst.txt').read())==len(open('tekst.txt').read().decode('utf8'))


32
31
False

2.4.2.1. Metoda encode()

Metoda encode() kodira niz znakova u navedenu kodnu stranicu. Kao argument metoda prima naziv kodne stranice (npr. utf8 ili iso-8859-2). Ova metoda koristi se pri pisanju u datoteku.

Za primjer stvorit ćemo novu datoteku tekst_iso.txt te kodirati u kodnu stranicu ISO-8859-2 sa sadržajem iz datoteke tekst.txt.


In [147]:
tekst=open('tekst.txt').read().decode('utf8')

iso=open('tekst_iso.txt','w')
iso.write(tekst.encode('iso-8859-2'))
iso.close()

Ako usporedimo dužine obiju datoteka bez dekodiranja, dobit ćemo da nisu jednake dužine iako sadrže isti tekst.


In [148]:
len(open('tekst.txt').read())==len(open('tekst_iso.txt').read())


Out[148]:
False

Međutim, ako usporedimo dužine datoteka s ispravnim kodiranjem, dobit ćemo da su jednake dužine:


In [149]:
duzina_utf=len(open('tekst.txt').read().decode('utf8'))
duzina_iso=len(open('tekst_iso.txt').read().decode('iso-8859-2'))
duzina_utf==duzina_iso


Out[149]:
True

Treba pripaziti da se ispravno dekodira datoteka i kod iteriranja. U sljedećem primjeru izračunat ćemo dužinu redova u datoteci tekst.txt bez dekodiranja i s ispravnim dekodiranjem:


In [150]:
for red in open('tekst.txt'):
    print red
    print 'Dužina reda: ' + str(len(red))


Prvi red.

Dužina reda: 10
Drugi red.

Dužina reda: 11
Treći red.
Dužina reda: 11

U ovom primjeru dužina posljednjeg reda je neispravno izračunata jer je slovo "ć" pročitano kao dva, umjesto kao jedan znak.


In [151]:
for red in open('tekst.txt'):
    red=red.decode('utf8')
    print 'Dužina reda: ' + str(len(red))


Dužina reda: 10
Dužina reda: 11
Dužina reda: 10

Prvi korak je iteracija kroz datoteku po redovima. Sada pomoćna varijabla red poprima sadržaj reda datoteke. U sljedećem koraku u istu pomoćnu varijablu red pohranjujemo sadržaj tog red, ali dekodiran UTF-8 kodnom stranicom. Sada pomoćna varijabla red poprima sadržaj reda datoteke, ali ispravno kodiran. Sljedeći put kada u istoj iteraciji koristimo pomoćnu varijablu red, ona sadrži ispravno kodiran red datoteke. U posljednjem redu ispisujemo dužinu reda. Upravo zbog ispravnog kodiranja i slova "ć", sada je ispravna vrijednost dužine posljednjeg reda 10, a ne 11.

Zadatke možete naći ovdje: Zadaci