Lukeminen tekstitiedostosta
Ennen kuin ohjelma aloittaa lukemisen tekstitiedostosta, on ohjelmalle
kerrottava, mitä käyttöjärjestelmän tiedostoa ohjelmassa tiedostosta
käytettävä muuttuja vastaa. Tämä tehdään open
-funktion avulla.
Funktiota käytetään seuraavasti:
tiedostomuuttuja = open(tiedoston_nimi, kasittelytapa)
Tässä tiedostomuuttuja
on ohjelmassa sen muuttujan nimi, jonka
avulla tiedostoa käsitellään, tiedoston_nimi
tiedostosta
käyttöjärjestelmän puolella käytettävä nimi ja kasittelytapa
kertoo,
aikooko ohjelma lukea tietoa tästä tiedostosta vai kirjoittaa siihen.
Funktion open
kutsumista sanotaan tiedoston avaamiseksi. Jos
tiedosto avataan lukemista varten, käsittelytavaksi annetaan "r"
(englannin sanasta read). Avattavan tiedoston on oltava samassa
hakemistossa kuin missä ohjelmaa ajetaan, tai muussa tapauksessa
tiedoston nimen pitää sisältää myös hakemistopolku.
Esimerkiksi seuraava käsky avaa tiedoston, jonka nimi on tekstia.txt
lukemista varten niin, että ohjelmassa tiedostoa voidaan käsitellä
muuttujan lahtotiedosto
avulla:
lahtotiedosto = open("tekstia.txt", "r")
Kun tiedosto on avattu, siitä voidaan lukea rivejä eri tavoin. Yksi tapa
on lukea tiedostosta rivi kerrallaan metodin readline
avulla
seuraavasti:
eka_rivi = lahtotiedosto.readline()
Tässä luettu rivi tallennettiin muuttujaan eka_rivi
. Metodi
readline
toimii siten, että kun sitä kutsutaan ensimmäisen kerran,
se palauttaa tiedoston ensimmäisen rivin, toinen kutsu palauttaa
tiedoston toisen rivin jne. Python-tulkki siis pitää kirjaa siitä, missä
kohdassa tiedostossa ollaan menossa, jolloin readline
-metodi osaa
aina lukea seuraavan vielä lukemattoman rivin. Jos metodia kutsutaan
siinä vaiheessa, kun tiedosto on jo luettu loppuun, metodi palauttaa
tyhjän merkkijonon ""
. Siitä tiedetään, että tiedosto on jo luettu
loppuun. (Jos tiedostossa on välissä rivejä, jolla ei ole tekstiä,
metodi ei niitä lukiessa palauta tyhjää merkkijonoa, vaan
merkkijonon, joka sisältää ainoastaan rivinvaihtomerkin "\n"
.)
Kun tiedoston lukeminen lopetetaan, on tiedosto syytä sulkea
close
-metodilla. Tämä vapauttaa tiedoston käsittelyyn
käyttöjärjestelmän puolella varatut resurssit.
Oletetaan, että tiedosto tekstia.txt
on seuraavan näköinen:
ensimmainen rivi
toinen rivi
viimeinen rivi
Voimme nyt kirjoittaa ohjelman, joka lukee tiedoston rivit ja tulostaa jokaisen rivin kuvaruudulle seuraavasti:
def main():
try:
lahtotiedosto = open("tekstia.txt", "r")
rivi = lahtotiedosto.readline()
while rivi != "":
print(rivi)
rivi = lahtotiedosto.readline()
lahtotiedosto.close()
except OSError:
print("Virhe tiedoston lukemisessa. Ohjelma paattyy.")
main()
readline
-metodin
kutsu palautti jotain järkevää eli tiedostoa ei ole vielä luettu
loppuun.Ohjelmessa on käytetty try–except-rakennetta käsittelemään mahdolliset
OSError
-tyyppiset poikkeukset. Tällainen poikkeus voi aiheutua
esimerkiksi silloin, jos tiedostoa tekstia.txt
ei ole lainkaan tai
sitä ei pystytä lukemaan esimerkiksi lukuoikeuksien puuttumisen tai
laitteistovian takia. (Katso lisätietoja käytetystä poikkeuksesta
kappaleesta 6.2.4.)
Jos ohjelma suoritetaan onnistuneesti, näyttää sen tulostus seuraavalta:
ensimmainen rivi
toinen rivi
viimeinen rivi
Ohjelma on siis tulostanut ylimääräisen rivinvaihdon jokaisen rivin
jälkeen. Tämä johtuu siitä, että metodi readline
palauttaa myös
rivin lopussa olevan rivinvaihtomerkin lukemansa merkkijonon lopussa.
Kun print
-käsky puolestaan lisää rivinvaihdon tulostuksensa loppuun,
tulee joka rivin jälkeen kaksi rivinvaihtoa: toinen, joka on tiedostosta
luetun rivin lopussa, ja toinen, jonka print
-käsky on lisännyt.
Ongelma voidaan ratkaista poistamalla tiedostosta luetun rivin lopussa
oleva rivinvaihtomerkki. Tämä voidaan tehdä esimerkiksi
rstrip
-metodilla. (Metodi poistaa myös muut luetun rivin lopussa
olevat ns. tyhjät merkit, esimerkiksi välilyönnit, tabuloinnit jne. Jos
tätä ei toivota, on rivinvaihtomerkki poistettava jollain muulla tavoin
– esimerkiksi ottamalla luetusta rivistä alimerkkijono, joka ei sisällä
rivin viimeistä merkkiä. Tämäkin tapa voi kuitenkin aiheuttaa vaikeuksia
käyttöjärjestelmässä, jossa rivinvaihtoa merkitään useammalla kuin
yhdellä merkillä.) Seuraavaa ohjelmaa on muutettu myös niin, että
luettavan tiedoston nimeä ei ole määrätty ohjelmassa, vaan se kysytään
käyttäjältä.
def main():
nimi = input("Anna luettavan tiedoston nimi: ")
try:
lahtotiedosto = open(nimi, "r")
rivi = lahtotiedosto.readline()
while rivi != "":
rivi = rivi.rstrip()
print(rivi)
rivi = lahtotiedosto.readline()
lahtotiedosto.close()
except OSError:
print("Virhe tiedoston", nimi, "lukemisessa. Ohjelma paattyy.")
main()
Ohjelman suoritus näyttää nyt seuraavalta. Ylimääräiset rivinvaihdot ovat hävinneet.
Anna luettavan tiedoston nimi: tekstia.txt
ensimmainen rivi
toinen rivi
viimeinen rivi
Toisessa esimerkissä yritetään lukea tiedostoa, jota ei ole olemassa.
Anna luettavan tiedoston nimi: olematon.txt
Virhe tiedoston olematon.txt lukemisessa. Ohjelma paattyy.
Edellä tiedoston rivejä on luettu while-käskyn avulla rivi kerrallaan. Jos rivit käydään läpi for-käskyn avulla, voidaan lukeminen kirjoittaa selvästi yksinkertaisemmin. Jos toistokäsky kirjoitetaan muotoon
for rivi in lahtotiedosto:
tee jotain riville rivi
huolehtii for-käsky siitä, että tiedoston jokainen rivi luetaan
vuorotellen ja sijoitetaan muuttujan rivi
arvoksi. Ohjelmaan ei
silloin tarvitse kirjoittaa lainkaan readline
-käskyjä, vaan
Python-tulkki huolehtiin rivien lukemisesta for-käskyä suorittaessaan.
Tiedoston lukeva ja sen rivit tulostava ohjelma voidaan siis kirjoittaa seuraavasti:
def main():
nimi = input("Anna luettavan tiedoston nimi: ")
try:
lahtotiedosto = open(nimi, "r")
for rivi in lahtotiedosto:
rivi = rivi.rstrip()
print(rivi)
lahtotiedosto.close()
except OSError:
print("Virhe tiedoston", nimi, "lukemisessa. Ohjelma paattyy.")
main()
Jos tiedosto luetaan for-käskyn avulla, ei samassa ohjelmassa kannata
yleensä käyttää lainkaan readline
-käskyä, koska sitä käytettäessä
for-käsky ei käy lainkaan läpi niitä rivejä, jotka luetaan
readline
-käskyllä. Poikkeuksena tästä ohjeesta on tilanne, jossa
tiedetään varmasti, että tiedoston ensimmäinen rivi on otsikkorivi,
jolla olevaa tietoa ei ole tarkoitus käsitellä ohjelmassa lainkaan.
Tällöin tiedoston avaamisen jälkeen voi ensin lukea otsikkorivin
readline
-käskyllä ja sen jälkeen käydä tiedoston loput rivit
läpi lukemalla ne for-käskyllä. Jos ohjelmassa on ennen tiedoston
rivit läpikäyvää for-käskyä yksi tai useampi readline
-käsky,
aloittaa for-käsky rivien lukemisen tiedostosta siitä kohdasta, mihin on
jääty readline
-käskyjen suorittamisen jälkeen. Tästä on esimerkki
seuraavan esimerkin jälkeen vähän alempana.
Oletetaan, että käyttäjä on kirjoittanut suuren juhlan vieraslistan
tiedostoon. Kunkin vieraan nimi on kirjoitettu omalle riville. Käyttäjä
haluaa sitten tarkistaa, onko jokin nimi vieraslistassa. Seuraava
ohjelma pyytää käyttäjältä tiedoston ja yhden nimen sekä tarkistaa, onko
käyttäjän antama nimi tiedostossa. Vaikka tiedostosta luettuja rivejä ei
nyt tulostetakaan, on rstrip
-metodin käyttäminen tässäkin
tapauksessa tarpeellista, sillä muuten tiedostosta luettujen nimien
lopussa olisi rivinvaihtomerkki eikä mikään niistä silloin olisi
merkkijonojen vertailussa yhtäsuuri käyttäjän antaman nimen kanssa.
def main():
nimi = input("Anna tiedoston nimi: ")
etsittava_nimi = input("Anna tiedostosta etsittava nimi: ")
loytyi = False
try:
lahtotiedosto = open(nimi, "r")
for rivi in lahtotiedosto:
rivi = rivi.rstrip()
if rivi == etsittava_nimi:
loytyi = True
lahtotiedosto.close()
if loytyi:
print("Nimi", etsittava_nimi, "loytyi tiedostosta.")
else:
print("Nimea", etsittava_nimi, "ei loytynyt.")
except OSError:
print("Virhe tiedoston", nimi, "lukemisessa. Ohjelma paattyy.")
main()
Jos kuitenkin tiedetään, että tiedostossa on nimilistan yläpuolella
ensimmäisenä otsikkorivi, jota ei ole tarkoitus tulkita kenenkään nimeksi,
voidaan tämä otsikkorivi ohittaa yhdellä readline
-käskyllä ennen
for-käskyä:
def main():
nimi = input("Anna tiedoston nimi: ")
etsittava_nimi = input("Anna tiedostosta etsittava nimi: ")
loytyi = False
try:
lahtotiedosto = open(nimi, "r")
lahtotiedosto.readline() # Ohitetaan otsikkorivi
for rivi in lahtotiedosto: # Aloittaa tiedoston toiselta rivilta
rivi = rivi.rstrip()
if rivi == etsittava_nimi:
loytyi = True
lahtotiedosto.close()
if loytyi:
print("Nimi", etsittava_nimi, "loytyi tiedostosta.")
else:
print("Nimea", etsittava_nimi, "ei loytynyt.")
except OSError:
print("Virhe tiedoston", nimi, "lukemisessa. Ohjelma paattyy.")
main()
Kolmas tapa lukea tiedostosta on käyttää metodia readlines()
. Tämä
metodi lukee kaikki tiedoston jäljellä olevat rivit listaan (kukin rivi
on yksi listan alkio). Sen jälkeen listassa olevia rivejä voidaan
käsitellä halutulla tavalla. Esimerkiksi seuraava ohjelma lukee
käyttäjän antamat rivit listaan ja tulostaa sitten listan kaikki alkiot
niin, että se poistaa tyhjät merkit kunkin rivin lopusta:
def main():
nimi = input("Anna luettavan tiedoston nimi: ")
try:
lahtotiedosto = open(nimi, "r")
rivilista = lahtotiedosto.readlines()
lahtotiedosto.close()
for rivi in rivilista:
rivi = rivi.rstrip()
print(rivi)
except OSError:
print("Virhe tiedoston", nimi, "lukemisessa. Ohjelma paattyy.")
main()
rivilista
niin,
että yksi tiedoston rivi on aina yksi listan alkio.rivilista
. Rivit
on luettu tiedostosta jo aikaisemmin ja tämä toistokäsky
käy läpi vain listaa, ei tiedostoa.Jos tiedostosta luetaan lukuja, on tiedostosta luettu rivi muutettava kokonais- tai desimaaliluvuksi ihan samalla tavalla kuin käyttäjän syötettä näppäimistöltäkin luettaessa. Tämä johtuu siitä, että rivit luetaan tiedostosta aina merkkijonoina niiden sisällöstä riippumatta. Oletetaan, että tiedostossa on annettu eri päivien lämpötiloja, kukin omalla rivillään. Seuraava ohjelma lukee lämpötilat ja laskee niiden keskiarvon.
def main():
nimi = input("Mista tiedostosta lampotilat luetaan: ")
summa = 0.0
lkm = 0
try:
lampotiedosto = open(nimi, "r")
for rivi in lampotiedosto:
rivi = rivi.rstrip()
lampotila = float(rivi)
summa += lampotila
lkm += 1
lampotiedosto.close()
if lkm == 0:
print("Tiedostossa ei ollut yhtaan lampotilaa.")
else:
keskiarvo = summa / lkm
print("Lampotilojen keskiarvo on", keskiarvo)
except OSError:
print("Virhe tiedoston", nimi, "lukemisessa. Ohjelma paattyy.")
except ValueError:
print("Virheellinen rivi tiedostossa", nimi, "- ohjelma paattyy.")
main()
Ohjelmassa on käytetty ValueError
-tyyppistä poikkeusta selvittämään
niitä virhetilanteita, joissa tiedostosta luettua riviä ei pystytä
muuttamaan luvuksi.
Jos tiedoston samalla rivillä on useita eri tietoja, esimerkiksi
päivämäärä ja lämpötila, pitää luettu rivi jakaa ensin osiin esimerkiksi
split
-metodilla. Tämän jälkeen käsittelyä voidaan jatkaa halutuista
osista. Seuraavassa esimerkkiohjelmassa lämpötilat luetaan tiedostosta,
jossa jokaisella rivillä on ensin päivämäärä (tekstinä) ja sen jälkeen
lämpötila. Oletetaan, että päivämäärä ja lämpötila on erotettu
toisistaan pilkulla eikä rivillä ole muita pilkkuja.
Kukin luettu rivi jaetaan ensin split
-metodilla kahteen osaan,
joista ensimmäisessä pitäisi olla päivämäärä (josta ohjelma ei ole
kiinnostunut) ja toisessa lämpötila. Ohjelma tarkistaa ensin, että osia
on todellakin kaksi. Jos ei ole, ohjelma ilmoittaa virheellisestä
rivistä ja tulostaa sen, mutta jatkaa kuitenkin toimintaansa lukemalla
seuraavan rivin. Jos osia on kaksi, jälkimmäinen niistä muutetaan
desimaaliluvuksi ja lasketaan mukaan lämpötilojen summaan samaan tapaan
kuin edellisessä esimerkissä. Jos tiedoston lukeminen ei onnistu tai
jollain rivillä on kaksi osaa, mutta jälkimmäistä niistä ei voi muuttaa
desimaaliluvuksi, ohjelma ilmoittaa virheestä ja lopettaa toimintansa.
Taulukkolaskentaohjelmien käsittelemiä tiedostoja voidaan tallentaa csv-tiedostoiksi, joissa tiedoston tiedot on tallennettu tekstitiedostoon riveittäin siten, että eri sarakkeet on erotettu toisistaan pilkulla. Tällaisia tiedostoja pystyy lukemaan ja käsittelemään Python-ohjelmassa juuri seuraavan esimerkin tavoin. (Monet taulukkolaskentaohjelmat antavat tiedostoa tallennettaessa käyttäjän valita sarakkeita erottavaksi merkiksi jonkin muunkin merkin pilkun sijaan.)
def main():
nimi = input("Mista tiedostosta lampotilat luetaan: ")
summa = 0.0
lkm = 0
try:
lampotiedosto = open(nimi, "r")
for rivi in lampotiedosto:
rivi = rivi.rstrip()
osat = rivi.split(",")
if len(osat) != 2:
print("Virheellinen rivi:", rivi)
else:
lampotila = float(osat[1])
summa += lampotila
lkm += 1
lampotiedosto.close()
if lkm == 0:
print("Tiedostossa ei ollut yhtaan lampotilaa.")
else:
keskiarvo = summa / lkm
print("Lampotilojen keskiarvo on", keskiarvo)
except OSError:
print("Virhe tiedoston", nimi,
"lukemisessa. Ohjelma paattyy.")
except ValueError:
print("Virheellinen lampotila tiedostossa", nimi,
"- Ohjelma paattyy.")
main()
osa
viittaa jaon tuloksena syntyneeseen listaan.lampotila
arvoksi rivin toinen osa. Se pitää kuitenkin ensin muuttaa
desimaaliluvuksi.Jos halutaan, että ohjelma ei lopeta toimintaansa virheellisen
lämpötilan lukemisen jälkeen, vaan jatkaa lukemalla seuraavan rivin,
kirjoitetaan ohjelmaan ValueError
-poikkeuksen käsittelevä
try-except-rakenne OSError
-poikkeuksen käsittelevän rakenteen ja
tiedoston rivejä lukevan toistokäskyn sisään. Ohjelma näyttää silloin
seuraavalta:
def main():
nimi = input("Mista tiedostosta lampotilat luetaan: ")
summa = 0.0
lkm = 0
try:
lampotiedosto = open(nimi, "r")
for rivi in lampotiedosto:
rivi = rivi.rstrip()
osat = rivi.split(",")
if len(osat) != 2:
print("Virheellinen rivi:", rivi)
else:
try:
lampotila = float(osat[1])
summa += lampotila
lkm += 1
except ValueError:
print("Virheellinen lampotila:", osat[1])
lampotiedosto.close()
if lkm == 0:
print("Tiedostossa ei ollut yhtaan lampotilaa.")
else:
keskiarvo = summa / lkm
print("Lampotilojen keskiarvo on", keskiarvo)
except OSError:
print("Virhe tiedoston", nimi,
"lukemisessa. Ohjelma paattyy.")
main()
ValueError
-poikkeuksen käsittelevä except
-osa on nyt tiedoston
rivit lukevan toistokäskyn sisällä. Vaikka jonkin rivin käsittelyssä
tapahtuisi virhe, siirrytään sen jälkeen toistokäskyn seuraavalle
kierrokselle lukemaan tiedoston seuraava rivi.
lahtotiedosto
viittaamaan avattuun tiedostoon.