Tiedostojen lukeminen
Yksi tavallinen ohjelmoinnin käyttötarkoitus on käsitellä tiedostoissa olevaa tietoa. Ohjelmat voivat lukea tietoa tiedostoista ja tallentaa tuloksia tiedostoihin. Tiedostojen avulla voimme käsitellä suuriakin aineistoja helposti automaattisesti.
Oletamme tällä kurssilla, että käsiteltävät tiedostot ovat tekstitiedostoja eli ne muodostuvat riveistä, joilla on tekstiä. Esimerkiksi kurssilla käytetty Visual Studio Code -editori käsittelee tekstitiedostoja. Huomaa, että esimerkiksi Word-dokumentti ei ole tekstitiedosto, vaan siinä on tekstin lisäksi muotoilutietoja ja sen käsittely ohjelmallisesti olisi vaikeaa.
Tiedostosta lukeminen
Käytetään esimerkkinä tiedostoa esimerkki.txt
, jonka sisältönä on:
Moi kaikki! Esimerkkitiedostomme on kolmerivinen. Viimeinen rivi.
Hyvä tapa käsitellä tiedostoja Pythonissa on käyttää with
-lausetta, jonka alkurivi avaa tiedoston. Tämän jälkeen tulee lohko, jonka sisällä tiedostoa voi käsitellä. Lohkon jälkeen tiedosto sulkeutuu automaattisesti, eikä sitä voi enää käsitellä.
Esimerkiksi seuraava koodi lukee ja tulostaa tiedoston sisällön:
with open("esimerkki.txt") as tiedosto:
sisalto = tiedosto.read()
print(sisalto)
Moi kaikki! Esimerkkitiedostomme on kolmerivinen. Viimeinen rivi.
Koodissa muuttuja tiedosto
on tiedostokahva, jonka kautta tiedostoa voi käsitellä avaamisen jälkeen. Tässä tapauksessa käytämme metodia read
, joka palauttaa koko tiedoston sisällön yhtenä merkkijonona. Tässä tapauksessa palautettu merkkijono on seuraava:
"Moi kaikki!\nEsimerkkitiedostomme on kolmerivinen.\nViimeinen rivi."
Tiedoston sisällön läpikäynti
Metodi read
on näppärä, jos halutaan esimerkiksi tulostaa tiedoston sisältö kokonaisuudessaan ruudulle. Usein haluamme kuitenkin käsitellä tiedostoa rivi kerrallaan.
Voimme käyttää tiedoston sisällön lukemiseen for
-silmukkaa, joka käy läpi tiedoston rivit yksi kerrallaan – siis samaan tapaan kuin esimerkiksi listan läpikäynnissä.
Seuraava esimerkki lukee saman tiedoston nyt käyttäen for
-silmukkaa, poistaa joka rivin perästä rivinvaihdon ja laskee rivien yhteispituuden:
with open("esimerkki.txt") as tiedosto:
laskuri = 0
yhteispituus = 0
for rivi in tiedosto:
rivi = rivi.replace("\n", "")
laskuri += 1
print("Rivi", laskuri, rivi)
pituus = len(rivi)
yhteispituus += pituus
print("Rivien yhteispituus:", yhteispituus)
Rivi 1 Moi kaikki! Rivi 2 Esimerkkitiedostomme on kolmerivinen. Rivi 3 Viimeinen rivi. Rivien yhteispituus: 63
Huomaa, että rivien läpikäynnissä jokaisen rivin perässä on rivinvaihto \n
. Yllä oleva koodi kuitenkin poistaa rivinvaihdot replace
-funktiolla, joka korvaa rivinvaihdot tyhjillä merkkijonoilla. Tämän ansiosta tulostukseen ei tule ylimääräisiä rivivaihtoja ja ohjelma laskee oikein tiedoston rivien yhteispituuden.
Mitä jos VS code ei löydä tiedostoja koodia suoritettaessa?
Jos VS Code ei löydä tiedostoa suorittaessasi koodia (vihreää nappia painamalla) vaikka olet tarkastanut tiedoston nimen kirjoitusasun, voit kokeilla seuraavaa:
- Mene asetuksiin valikosta File -> Preferences -> Settings
- Etsi muutettava kohta hakusanalla "executeinfile"
- Valitse välilehti Workspace
- Laita raksi kohtaan Python -> Terminal -> Execute In File Dir
Oikein tehtynä asetus näyttää suunilleen seuraavalta:
Jos edellinenkään ei toimi, voit kopioida kansiossa src olevan testaukseen käytetyn tiedoston sisällön
suoraan tehtäväkansion alle
Tiedostoja lukevan koodin debuggaus
Jos yrität käyttää VS Coden debuggeria tiedostoja lukevan koodin suorittamiseen, törmäät ikävään virheilmoitukseen:
Syynä tälle on se, että debuggeri etsii tiedostoja tehtäväkansion juuresta eikä edes Execute In File Dir -asetus ei asiaa muuta. Helpoin ratkaisu ongelmaan on edellisessä luvussa kuvattu testaukseen käytetyn tiedoston kopioiminen tehtävähakemiston juureen.
Kun olet kopioinut tiedostot tehtävähakemiston juureen, joudut ehkä vielä käynnistämään visual studio coden uudelleen jotta kaikki toimisi.
CSV-tiedoston lukeminen
CSV-tiedosto (Comma Separated Values) on tekstitiedosto, jonka jokaisella rivillä on tietyllä välimerkillä erotettua tietoa. Välimerkkinä on usein pilkku ,
tai puolipiste ;
, mutta mikä tahansa muukin merkki on periaatteessa mahdollinen.
CSV-tiedostoja käytetään usein erilaisten aineistojen esittämiseen. Myös Excelin ja muiden vastaavien ohjelmien taulukot voidaan tallentaa CSV-muodossa, jolloin niitä on helppo käsitellä muilla ohjelmilla.
Voimme lukea CSV-tiedoston rivit for
-silmukalla, mutta miten erottaa rivillä olevat tiedot toisistaan? Helppo tapa on käyttää merkkijonojen split
-metodia: metodille annetaan haluttu välimerkki, ja se palauttaa tiedot välimerkin mukaan eroteltuna listana merkkijonoja.
Esimerkki metodin käytöstä:
teksti = "apina,banaani,cembalo"
sanat = teksti.split(",")
for sana in sanat:
print(sana)
apina banaani cembalo
Tarkastellaan esimerkkinä tiedostoa arvosanat.csv
, joka sisältää jokaisella rivillä aluksi opiskelijan nimen ja sen jälkeen tämän eri kursseista saamat arvosanat. Tiedot on erotettu toisistaan puolipisteillä.
Pekka;5;4;5;3;4;5;5;4;2;4 Paula;3;4;2;4;4;2;3;1;3;3 Pirjo;4;5;5;4;5;5;4;5;4;4
Seuraava ohjelma käy läpi tiedoston rivit, jakaa jokaisen rivin osiin ja näyttää opiskelijan nimen sekä arvosanat.
with open("arvosanat.csv") as tiedosto:
for rivi in tiedosto:
rivi = rivi.replace("\n", "")
osat = rivi.split(";")
nimi = osat[0]
arvosanat = osat[1:]
print("Nimi:", nimi)
print("Arvosanat:", arvosanat)
Nimi: Pekka Arvosanat: ['5', '4', '5', '3', '4', '5', '5', '4', '2', '4'] Nimi: Paula Arvosanat: ['3', '4', '2', '4', '4', '2', '3', '1', '3', '3'] Nimi: Pirjo Arvosanat: ['4', '5', '5', '4', '5', '5', '4', '5', '4', '4']
Saman tiedoston lukeminen moneen kertaan
Joissain tilanteissa ohjelman on tarvetta lukea sama tiedosto useampaan kertaan. Tarkastellaan esimerkkinä seuraavaa ohjelmaa, joka käsittelee henkilötietoja sisältävää tiedostoa:
with open("henkilot.csv") as tiedosto:
# tulostetaan nimet
for rivi in tiedosto:
osat = rivi.split(";")
print("Nimi:", osat[0])
# etsitään vanhin
vanhimman_ika = -1
for rivi in tiedosto:
osat = rivi.split(";")
nimi = osat[0]
ika = int(osat[1])
if ika > vanhimman_ika:
vanhimman_ika = ika
vanhin = nimi
print("vanhin on", vanhin)
Ohjelma aiheuttaa erikoisen virheilmoituksen:
Traceback (most recent call last):
print("vanhin on"; vanhin)
UnboundLocalError: local variable 'vanhin' referenced before assignment
Syynä virheelle on se, että jälkimmäistä for-silmukkaa ei suoriteta ollenkaan, sillä tiedoston voi lukea vain kerran. Tämän jälkeen ollaan päästy "tiedoston loppuun", ja vaikka yritetään lukea tiedostosta lisää jälkimmäisessä silmukassa, tietoon ei päästä enää käsiksi.
Tiedosto onkin avattava uudelleen komennolla open
toista lukukertaa varten:
with open("henkilot.csv") as tiedosto:
# tulostetaan nimet
for rivi in tiedosto:
osat = rivi.split(";")
print("Nimi:", osat[0])
with open("henkilot.csv") as tiedosto:
# etsitään vanhin
vanhimman_ika = -1
for rivi in tiedosto:
osat = rivi.split(";")
nimi = osat[0]
ika = int(osat[1])
if ika > vanhimman_ika:
vanhimman_ika = ika
vanhin = nimi
print("vanhin on", vanhin)
Yleensä aina on kuitenkin parasta lukea tiedosto vain kerran ja tallentaa se muotoon, jota ohjelman toiminnallisuudet pystyvät hyödyntämään:
henkilot = []
# luetaan tiedostosta henkilöt listaan
with open("henkilot.csv") as tiedosto:
for rivi in tiedosto:
osat = rivi.split(";")
henkilot.append((osat[0], int(osat[1]), osat[2]))
# tulostetaan nimet
for henkilo in henkilot:
print("Nimi:", henkilo[0])
# etsitään vanhin
vanhimman_ika = -1
for henkilo in henkilot:
nimi = henkilo[0]
ika = henkilo[1]
if ika > vanhimman_ika:
vanhimman_ika = ika
vanhin = nimi
print("vanhin on", vanhin)
Lisää CSV-tiedoston käsittelyä
Jatketaan opiskelijoiden arvosanoja sisältävän tiedoston arvosanat.csv
käsittelyä. Tiedosto näyttää siis seuraavalta:
Pekka;5;4;5;3;4;5;5;4;2;4 Paula;3;4;2;4;4;2;3;1;3;3 Pirjo;4;5;5;4;5;5;4;5;4;4
Seuraava ohjelma luo tiedoston perusteella sanakirjan arvosanat
, jossa jokainen avain on opiskelijan nimi ja vastaava arvo on lista arvosanoista. Ohjelma muuttaa arvosanat kokonaisluvuiksi, jotta niitä on mukavampaa käsitellä myöhemmin.
arvosanat = {}
with open("arvosanat.csv") as tiedosto:
for rivi in tiedosto:
rivi = rivi.replace("\n", "")
osat = rivi.split(";")
nimi = osat[0]
arvosanat[nimi] = []
for arvosana in osat[1:]:
arvosanat[nimi].append(int(arvosana))
print(arvosanat)
{'Pekka': [5, 4, 5, 3, 4, 5, 5, 4, 2, 4], 'Paula': [3, 4, 2, 4, 4, 2, 3, 1, 3, 3], 'Pirjo': [4, 5, 5, 4, 5, 5, 4, 5, 4, 4]}
Tämän jälkeen voimme vaikkapa tulostaa analyysin arvosanoista käymällä läpi sanakirjan arvosanat
perusteella:
for nimi, lista in arvosanat.items():
paras = max(lista)
keskiarvo = sum(lista) / len(lista)
print(f"{nimi}: paras arvosana {paras}, keskiarvo {keskiarvo:.2f}")
Pekka: paras arvosana 5, keskiarvo 4.10 Paula: paras arvosana 4, keskiarvo 2.90 Pirjo: paras arvosana 5, keskiarvo 4.50
Kannattaa tutustua huolella esimerkkikoodiin. Se voi ensisilmäyksellä vaikuttaa monimutkaiselta, mutta ratkaisu on helposti sovellettavissa monenlaisiin datatiedostoihin.
Eroon turhista riveistä, välilyönneistä ja rivinvaihdoista
Olemme tallentaneet Excelistä nimiä taulukon CSV-muodossa:
etunimi; sukunimi
Pekka; Python
Jaana; Java
Heikki; Haskell
Kuten tyypillistä, Excel on lisännyt sarakkeiden väliin erottimena toimivan puolipisteen lisäksi myös välilyönnin.
Haluamme tulostaa listalla olevat sukunimet. Koska ensimmäinen rivi kertoo sarakkeiden otsikot, ohitamme sen:
sukunimet = []
with open("henkilot.csv") as tiedosto:
for rivi in tiedosto:
osat = rivi.split(";")
# ohitetaan otsikkorivi
if osat[0] == "etunimi":
continue
sukunimet.append(osat[1])
print(sukunimet)
Tulostus näyttää seuraavalta:
[' Python\n', ' Java\n', ' Haskell']
Kaikkiin paitsi viimeiseen rivin sukunimeen on jäänyt mukaan rivinvaihtomerkki, ja jokaisen sukunimen alkuun on jäänyt ikävä välilyönti.
Pääsisimme näistä eroon aiempien esimerkkien tapaan käyttämällä metodia replace
, mutta parempi vaihtoehto tässä tilanteessa on käyttää metodia strip
, joka poistaa merkkijonon alusta ja lopusta ns. whitespace-merkit, eli välilyönnit, rivinvaihdot ja muut normaalina merkkinä tulostumattomat merkit.
Kokeillaan metodin toimintaa konsolissa:
>>> " koe ".strip()
'koe'
>>> "\n\ntesti\n".strip()
'testi'
>>>
Tarvittava muutos ohjelmaan on helppo:
sukunimet = []
with open("henkilot.csv") as tiedosto:
for rivi in tiedosto:
osat = rivi.split(';')
if osat[0] == "etunimi":
continue # tämä oli otsikkorivi, ei huomioida!
sukunimet.append(osat[1].strip())
print(sukunimet)
Tämän jälkeen tulostus on halutunlainen:
['Python', 'Java', 'Haskell']
Merkkijonoilla on myös metodit lstrip
ja rstrip
, jotka poistavat ainoastaan merkkijonon vasemmalla tai oikealla puolella olevia merkkejä.
>>> " testimerkkijono ".rstrip()
' testimerkkijono'
>>> " testimerkkijono ".lstrip()
'testimerkkijono '
Eri tiedostoissa olevien tietojen yhdistely
On hyvin yleistä, että ohjelmassa tarvittava data on talletettu useaan erilliseen tiedostoon. Tarkastellaan esimerkkinä tilannetta, jossa yrityksen henkilöstön tiedot ovat omassa tiedostossaan tyontekijat.csv
:
hetu;nimi;osoite;kaupunki
080488-123X;Pekka Mikkola;Vilppulantie 7;00700 Helsinki
290274-044S;Liisa Marttinen;Mannerheimintie 100 A 10;00100 Helsinki
010479-007Z;Arto Vihavainen;Pihapolku 4;01010 Kerava
010499-345K;Leevi Hellas;Tapiolantie 11 B;02000 Espoo
Työntekijöiden palkat taas ovat talletettu omaan tiedostoonsa palkat.csv
hetu;palkka;bonus
080488-123X;3300;0
290274-044S;4150;200
010479-007Z;1300;1200
Molempien tiedostojen riveillä on ensin henkilötunnus, joka kertoo kenen tiedoista on kyse. Käyttämällä henkilötunnusta yhdistävänä tekijänä, on helppo yhdistää henkilöiden nimet ja palkat toisiinsa, ja tehdä esimerkiksi ohjelma, joka tulostaa seuraavanlaisen näkymän henkilöiden ansioihin:
ansiot: Pekka Mikkola 3300 euroa Liisa Marttinen 4350 euroa Arto Vihavainen 2500 euroa
Ohjelma käyttää aputietorakenteena kahta sanakirjaa nimet
ja palkat
, joissa molemmissa avaimena toimii henkilötunnus:
nimet = {}
with open("tyontekijat.csv") as tiedosto:
for rivi in tiedosto:
osat = rivi.split(';')
if osat[0] == "hetu":
continue
nimet[osat[0]] = osat[1]
palkat = {}
with open("palkat.csv") as tiedosto:
for rivi in tiedosto:
osat = rivi.split(';')
if osat[0] == "hetu":
continue
palkat[osat[0]] = int(osat[1]) +int(osat[2])
print("ansiot:")
for hetu, nimi in nimet.items():
if hetu in palkat:
palkka = palkat[hetu]
print(f"{nimi:16} {palkka} euroa")
else:
print(f"{nimi:16} 0 euroa")
Ohjelma siis muodostaa ensin sanakirjat nimet
ja palkat
, joiden sisältö näyttää seuraavilta:
{
'080488-123X': 'Pekka Mikkola',
'290274-044S': 'Liisa Marttinen',
'010479-007Z': 'Arto Vihavainen',
'010499-345K': 'Leevi Hellas'
}
{
'080488-123X': 3300,
'290274-044S': 4350,
'010479-007Z': 2500
}
Lopun for-silmukka yhdistää henkilöiden nimet ja niitä vastaavat palkat sanakirjojen avulla.
Ohjelma huomioi myös tilanteen, jossa henkilön palkkatietoja ei ole olemassa.
Huomaa, että koska ohjelma käyttää aputietorakenteena sanakirjaa, ei henkilöitä vastaavien rivien järjestyksellä ole merkitystä.
Log in to view the quiz