Kirjautuminen

Haku

Tehtävät

Opasarkisto: Python 2 -ohjelmointi: Osa 8 - Tiedostot ja virheet

  1. Osa 1 - Ensimmäinen ohjelma
  2. Osa 2 - Tiedon käsittely
  3. Osa 3 - Ehtorakenteet
  4. Osa 4 - Toistorakenteet
  5. Osa 5 - Listojen käsittely
  6. Osa 6 - Merkkijonot
  7. Osa 7 - Omat funktiot
  8. Osa 8 - Tiedostot ja virheet
  9. Osa 9 - Standardikirjasto
  10. Osa 10 - Tietorakenteet
  11. Osa 11 - Alkeita edemmäs
  12. Osa 12 - Yhteenveto
  13. Liite 1 - Asennus ja käyttö
  14. Liite 2 - Python ja ääkköset

Kirjoittaja: Antti Laaksonen (2009).

⚠ Huomio! Tämä opas on vanhentunut. Oppaan sisältöön ei voi enää luottaa. Opas on säilytetty vain sen historiallisen arvon vuoksi. ⚠


Ohjelma voi säilyttää tietoa suorituskertojensa välillä tiedostoissa. Esimerkiksi ohjelma voi tallentaa pelin ennätyslistan tiedostoon, jotta ennätykset eivät häviä, kun ohjelman suoritus päättyy. Lisäksi ohjelma voi käsitellä toisten ohjelmien luomia tiedostoja.

Tekstitiedosto muodostuu riveistä, joilla on tekstimuotoista tietoa, kun taas binääritiedoston rakenne voi olla millainen tahansa. Tässä oppaassa kaikki tiedostot ovat tekstitiedostoja, koska niiden käsittely on helppoa ja ne riittävät useimpiin tarkoituksiin.

Tiedostojen käsittelyyn liittyy myös virheiden käsittely: esimerkiksi ohjelma voi yrittää lukea tietoa tiedostosta, jota ei ole olemassa. Tämän vuoksi oppaan lopuksi selviää, miten ohjelmassa voi varautua suorituksen aikana ilmeneviin virhetilanteisiin.

Tiedoston käsittely

Tiedoston käsittelyn yleisrakenne on seuraava:

tiedosto = open(nimi, avaustapa)
# tiedon luku tai kirjoitus
tiedosto.close()

Funktio open avaa tiedoston eli aloittaa sen käsittelyn. Funktio luo tiedosto-olion, jonka kautta ohjelma voi käsitellä tiedostoa. Funktion parametrit ovat tiedoston nimi ja avaustapa (lukeminen tai kirjoitus). Lopuksi metodi close sulkee tiedoston eli lopettaa sen käsittelyn.

Tiedoston avaustapoja ovat:

avaustapaselitys
"r"tiedostosta lukeminen
"w"tiedostoon kirjoitus (korvaa vanhan sisällön)
"a"tiedostoon kirjoitus (vanhan sisällön perään)

Tiedostosta lukeminen

Seuraavaksi oletetaan, että samassa hakemistossa ohjelman kanssa on tiedosto tarina.txt, jossa on seuraavat viisi riviä:

Henrikki on urhea ritari,
syntynyt vuonna 1672 kaukana täältä.
Henrikki on tämän pelin sankari.
Nyt Henrikki on 25 vuotta vanha,
eletään vuotta 1697 siis.

Seuraava ohjelma lukee tiedoston rivit listaan:

# -*- coding: latin-1 -*-
tiedosto = open("tarina.txt", "r")
rivit = tiedosto.readlines()
tiedosto.close()
print "Rivimäärä:", len(rivit)
print "Ensimmäinen rivi:"
print rivit[0],
print "Kaikki rivit:"
for rivi in rivit:
    print rivi,

Ohjelman tulostus on seuraava:

Rivimäärä: 5
Ensimmäinen rivi:
Henrikki on urhea ritari,
Kaikki rivit:
Henrikki on urhea ritari,
syntynyt vuonna 1672 kaukana täältä.
Henrikki on tämän pelin sankari.
Nyt Henrikki on 25 vuotta vanha,
eletään vuotta 1697 siis.

Tässä metodi readlines lukee tiedoston rivit listaan, minkä jälkeen rivit[0] on ensimmäinen rivi, rivit[1] on toinen rivi, rivit[2] on kolmas rivi jne. Tiedoston rivit ovat listassa merkkijonoina, ja jokaisen rivin lopussa on rivinvaihto, minkä vuoksi ohjelma ei tulosta print-komennoissa toista rivinvaihtoa.

Tiedoston rivejä voi myös lukea yksi kerrallaan:

# -*- coding: latin-1 -*-
tiedosto = open("tarina.txt", "r")
while True:
    rivi = tiedosto.readline()
    if rivi == "":
        break
    print rivi,
tiedosto.close()

Ohjelman tulostus on seuraava:

Henrikki on urhea ritari,
syntynyt vuonna 1672 kaukana täältä.
Henrikki on tämän pelin sankari.
Nyt Henrikki on 25 vuotta vanha,
eletään vuotta 1697 siis.

Tässä metodi readline lukee yhden rivin kerrallaan tiedostosta. Silmukassa muuttujassa rivi on vuorollaan jokainen tiedoston rivi. Kun kaikki tiedoston rivit on luettu, metodi readline palauttaa tyhjän merkkijonon. Jos tiedosto on suuri, tämän tavan etuna on, että vain yksi tiedoston rivi on kerrallaan muistissa.

Tiedostoon kirjoitus

Seuraava ohjelma kirjoittaa tiedostoon kolme riviä:

# -*- coding: latin-1 -*-
tiedosto = open("rivit.txt", "w")
tiedosto.write("Tästä alkaa tiedosto.\n")
tiedosto.write("Sitten tulee toinen rivi.\n")
tiedosto.write("Yhteensä rivejä on kolme.\n")
tiedosto.close()

Tiedoston rivit.txt sisällöksi tulee seuraava:

Tästä alkaa tiedosto.
Sitten tulee toinen rivi.
Yhteensä rivejä on kolme.

Jos tiedostoa ei ole olemassa, ohjelma luo sen samalla. Tässä avaustapa "w" tarkoittaa, että tiedoston mahdollinen vanha sisältö katoaa. Jos avaustapa olisi "a", ohjelma kirjoittaisi uudet rivit tiedoston vanhan sisällön perään.

Seuraava ohjelma kirjoittaa tiedostoon luvut 0–999:

# -*- coding: latin-1 -*-
tiedosto = open("luvut.txt", "w")
for luku in range(1000):
    tiedosto.write(str(luku) + "\n")
tiedosto.close()

Tiedoston luvut.txt sisällöksi tulee seuraava:

0
1
2
... (välissä rivejä)
997
998
999

Esimerkki: Käyttäjän tiedot

Seuraava ohjelma säilyttää käyttäjän tietoja tiedostossa:

# -*- coding: latin-1 -*-
uusi = raw_input("Oletko uusi käyttäjä (k/e)? ")
if uusi == "k":
    etunimi = raw_input("Etunimi: ")
    sukunimi = raw_input("Sukunimi: ")
    tiedosto = open("tiedot.txt", "w")
    tiedosto.write(etunimi + "\n")
    tiedosto.write(sukunimi + "\n")
    tiedosto.close()
    print "Kiitos tiedoista!"
else:
    tiedosto = open("tiedot.txt", "r")
    etunimi = tiedosto.readline().strip()
    sukunimi = tiedosto.readline().strip()
    tiedosto.close()
    print "Tervetuloa, " + etunimi + " " + sukunimi + "!"

Tässä on ohjelman kaksi peräkkäistä tulostusta:

Oletko uusi käyttäjä (k/e)? k
Etunimi: Antti
Sukunimi: Laaksonen
Kiitos tiedoista!
Oletko uusi käyttäjä (k/e)? e
Tervetuloa, Antti Laaksonen!

Tiedoston tiedot.txt sisältö taas on lopuksi seuraava:

Antti
Laaksonen

Kun ohjelma lataa tiedostosta etunimen ja sukunimen, se poistaa niiden lopussa olevat rivinvaihdot metodilla strip. Tämän jälkeen nimen osat voi yhdistää tavalliseen tapaan tervehdykseen.

Virheenkäsittely

Tiedostojen käsittelyssä virhetilanteet ovat mahdollisia. Yleinen virhetilanne on, että ohjelma yrittää lukea tietoa olemattomasta tiedostosta. Esimerkiksi jos tiedosto uolevi.txt puuttuu, seuraavan ohjelman suoritus keskeytyy virhetilanteeseen:

# -*- coding: latin-1 -*-
tiedosto = open("uolevi.txt", "r")
print tiedosto.readline()
tiedosto.close()

Ohjelman tulostus voi olla tällöin seuraava:

Traceback (most recent call last):
  File "C:\python\opas\uolevi.py", line 2, in <module>
    tiedosto = open("uolevi.txt", "r")
IOError: [Errno 2] No such file or directory: 'uolevi.txt'

Seuraava ohjelma varautuu virhetilanteeseen:

# -*- coding: latin-1 -*-
try:
    tiedosto = open("uolevi.txt", "r")
    print tiedosto.readline()
    tiedosto.close()
except IOError:
    print "Tiedostovirhe!"

Nyt ohjelman tulostus on seuraava, jos tiedosto puuttuu:

Tiedostovirhe!

Virheenkäsittely muodostuu try-osasta ja except-osista. Alussa olevan try-osan sisällä on koodi, jossa saattaa syntyä virhetilanne. Sitten except-osissa on koodia, joka suoritetaan virhetilanteessa. Eri virhetilanteilla on omat tunnuksensa, jotka näkyvät virheilmoituksissa: tässä IOError tarkoittaa virhettä tiedoston käsittelyssä.

Seuraava ohjelma varautuu käyttäjän virheisiin. Virhe ValueError tapahtuu, jos käyttäjän antamaa merkkijonoa ei voi muuttaa kokonaisluvuksi int-funktiolla. Virhe ZeroDivisionError tapahtuu, jos toinen luku on nolla eikä jakolasku ole mahdollinen.

# -*- coding: latin-1 -*-
try:
    luku1 = int(raw_input("Luku 1: "))
    luku2 = int(raw_input("Luku 2: "))
    tulos = luku1 / luku2
    print luku1, "/", luku2, "=", tulos
except ValueError:
    print "Virheellinen luku!"
except ZeroDivisionError:
    print "Nollalla ei saa jakaa!"

Tässä on ohjelman mahdollisia tulostuksia:

Luku 1: 15
Luku 2: 5
15 / 5 = 3
Luku 1: abc
Virheellinen luku!
Luku 1: 4
Luku 2: 0
Nollalla ei saa jakaa!

Esimerkki: Sanatutkimus

Tässä on suomen kielen sanalista, jossa on yli 90000 sanaa:

https://www.ohjelmointiputka.net/tiedostot/kotus_sanat.zip

Seuraava ohjelma etsii sanat, jotka sisältävät kaikki suomen kielen vokaalit:

# -*- coding: latin-1 -*-

try:
    tiedosto = open("kotus_sanat.txt", "r")
    sanat = tiedosto.readlines()
    tiedosto.close()
    print "Sanoja muistissa:", len(sanat)
except IOError:
    print "Tiedostovirhe!"
    sanat = []

print

for sana in sanat:
    sana = sana.strip()
    virhe = False
    for vokaali in "aeiouyäö":
        if vokaali not in sana:
            virhe = True
            break
    if not virhe:
        print sana

Ohjelman tulostus on seuraava:

Sanoja muistissa: 91591

arvostelukyvyttömästi
juustohöyläperiaate
kotitaloustyöntekijä
maataloustyöntekijä
myöhäiselokuva
suojatyöntekijä
taloustyöntekijä
ulkomaantyöntekijä
valkokaulustyöläinen
valkokaulustyöntekijä
ympäristönsuojelija

Ohjelma lukee tiedostosta sanat listaan sanat. Sitten ohjelma käy läpi kaikki sanat ja tulostaa niistä ne, jotka sisältävät kaikki vokaalit. Muuttujan virhe arvoksi tulee True, jos jokin vokaali puuttuu sanasta.


Kommentit

Mirox [07.10.2014 03:55:15]

#

Morjens.

En tahdo oikeen saada tota "Esimerkki: Sanatutkimus" koodia toimimaan.
tulostaa kyllä muistissa olevien sanojen määrän oikein mutta ei tulosta sanoja joissa on kaikki vokaalit (Olen myös kokeillut copy/pastea koodin mutta samalla tuloksella). Olisi hauska tietää mistä kyseinen virhe johtuu.

Metabolix [07.10.2014 21:16:35]

#

Mirox, virhe johtuu luultavasti väärästä merkistöstä.

Chiman [20.06.2015 12:57:45]

#

Tämä opas keskittyy Pythonin perusteisiin ja hyvä niin. Näytän kuitenkin jatko-opiskelua motivoivana esimerkkinä, miten pidemmästä koodista saa lyhyen ja silti selkeän all-funktion ja generaattorilausekkeen avulla. Myöhemmin tämän oppaan osassa 11 (Alkeita edemmäs) mainitaan listakooste. Generaattorilauseke on läheistä sukua sille.

Tämän sivun Sanatutkimus-koodissa oli kohta, jossa tulostetaan listan kaikki sanat, joissa esiintyy kaikki vokaalit:

for sana in sanat:
    sana = sana.strip()
    virhe = False
    for vokaali in "aeiouyäö":
        if vokaali not in sana:
            virhe = True
            break
    if not virhe:
        print sana

Generaattorilausekkeen ja all-funktion avulla sama toiminnallisuus näyttää tältä:

for sana in sanat:
    sana = sana.strip()
    if all(v in sana for v in 'aeiouyäö'):
        print sana

Tässä all-funktion sisällä on generaattorilauseke (... for ... in ...), jossa muuttuja v käy vuorotellen läpi kaikki vokaalit a-ö ja jokaisella kierroksella tarkistetaan onko kyseinen vokaali tutkittavassa sanassa (v in sana). Funktio all palauttaa True vain, jos kaikki vokaalit löytyvät, muuten False.

Kirjoita kommentti

Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.

Muista lukea kirjoitusohjeet.
Tietoa sivustosta