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.
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_name
već 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
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
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
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:
C:\Python27\
Users\Korisnik\
.
In [128]:
f=open('datoteka.txt').read()
print f
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
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))
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
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
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
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
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))
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
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
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).
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]:
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]:
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.
In [141]:
'šećer'.decode('utf8')
Out[141]:
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]:
In [143]:
len('šećer')==len('šećer'.decode('utf8'))
Out[143]:
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
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
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'))
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]:
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]:
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))
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))
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