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()
Avataan tiedosto ja laitetaan muuttuja lahtotiedosto viittaamaan avattuun tiedostoon.
Luetaan tiedoston ensimmäinen rivi.
Jatketaan lukemista niin kauan kuin edellinen readline-metodin kutsu palautti jotain järkevää eli tiedostoa ei ole vielä luettu loppuun.
Luetaan tiedoston seuraava rivi.
Tämä käsky suoritetaan vain silloin, jos tiedoston avaamisessa tai lukemisessa tulee virhetilanne.

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()
Tämä käsky lukee kaikki tiedoston rivit listaan rivilista niin, että yksi tiedoston rivi on aina yksi listan alkio.
Tässä toistokäskyssä käydään läpi listaa 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()
Toistokäsky, joka lukee yhdellä kierroksella aina yhden tiedoston rivin.
Jaetaan viimeksi luettu rivi osiin aina pilkun kohdalta. Muuttuja osa viittaa jaon tuloksena syntyneeseen listaan.
Jos tämä ehto on tosi, rivillä on väärä määrä toisistaan pilkulla erotettuja osia.
Jos rivillä on oikea määrä osia, sijoitetaan muuttujan lampotila arvoksi rivin toinen osa. Se pitää kuitenkin ensin muuttaa desimaaliluvuksi.
Jos tiedoston avaamisessa tai lukemisessa tulee virhe, hypätään tänne.
Jos jollain rivillä lämpötilan paikalla olevaa osaa ei pystytä muuttamaan desimaaliluvuksi, hypätään tänne.

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.
Jos tiedoston avaamisessa tai lukemisessa tulee virhe, hypätään tänne ja lopetetaan ohjelman suoritus.