10. MODUL · PYTHON

Fájlkezelés.

Szöveges, CSV és JSON fájlok

1. fejezet

Miért kell fájl?

A változók tartalma a program futása végén elvész. Ha az adatoknak meg kell maradniuk – egy pontszámtábla, a beállítások, egy napló –, vagy ha a programnak mások által előállított adatokat kell feldolgoznia (egy osztálynévsort, mérési eredményeket), fájlba írunk, illetve fájlból olvasunk.

Szöveges és bináris fájl

A szöveges fájl (.txt, .csv, .json, .py) karakterekből álló sorokat tartalmaz; ezt egy egyszerű szövegszerkesztő is megnyitja. A bináris fájl (kép, hang, PDF) tetszőleges bájtokból áll, és csak az adott formátumot ismerő program érti. A szöveges fájlnál a karakterkódolás mondja meg, melyik bájtsorozat melyik betűt jelenti: a mai szabvány az UTF-8, ezzel az ékezetes betűk is biztosan jól jelennek meg.

Hogyan működik itt? A böngészőben futó Python egy saját, a memóriában lévő munkakönyvtárral dolgozik. A példák alatt a „Munkakönyvtár” sáv mutatja a fájlokat: kattints egy fájl nevére, és megnézheted a tartalmát. Amit a program létrehoz vagy módosít, az zölden jelenik meg, és a következő futtatáskor is ott lesz – a ↺ gomb állítja vissza a kiinduló állapotot. A saját gépeden ugyanez a kód a valódi fájlrendszerben dolgozik: a relatív fájlnevet (adat.txt) az aktuális munkakönyvtárhoz képest keresi, vagyis ahhoz a mappához, ahonnan a programot elindítottad (VS Code-ban a megnyitott mappa). Tedd az adatfájlt a .py mellé, és nyisd meg a mappát VS Code-ban (Fájl → Mappa megnyitása…). Ha mégis FileNotFoundError jön, írasd ki, hol keresi a fájlt: import os, majd print(os.getcwd()).

2. fejezet

Megnyitás és bezárás: open() és with

A fájllal való munka három lépés: megnyitás, olvasás vagy írás, bezárás. Az open() függvény egy fájlobjektumot ad vissza, amelyen keresztül olvasunk és írunk.

with open("fajlnev.txt", "r", encoding="utf-8") as f:
    tartalom = f.read()
# itt a fájl már be van zárva
  • "fajlnev.txt"a fájl neve (a munkakönyvtárhoz képest)
  • "r"a megnyitás módja (lásd a táblázatot)
  • encoding="utf-8"a karakterkódolás – mindig add meg.
  • as fa fájlobjektum neve a blokkban
MódJelentésHa a fájl nem létezikHa létezik
"r"olvasás (ez az alapértelmezett)FileNotFoundErroraz elejéről olvas
"w"íráslétrehozzakiüríti, majd az elejére ír
"a"hozzáfűzés (append)létrehozzaa végére ír, a régi tartalom megmarad
"x"kizárólagos létrehozáslétrehozzaFileExistsError
"rb", "wb"bináris olvasás / írásmint fent, de bájtokkal (nincs encoding)

A with blokk végén a Python automatikusan bezárja a fájlt – akkor is, ha a blokkban hiba történt. Bezárás nélkül a kiírt adat egy része a memóriában ragadhat, és (főleg Windowson) más program nem tudja törölni vagy átnevezni a fájlt. A régebbi, with nélküli forma (f = open(…) … f.close()) is működik, de ott a bezárásról neked kell gondoskodnod.

3. fejezet

Olvasás

MódszerEredmény
f.read()a teljes (hátralévő) tartalom egyetlen szövegként
f.readline()a következő sor (a végén a \n-nel); a fájl végén üres szöveg
f.readlines()az összes sor egy listában (mindegyik végén \n)
for sor in f:soronkénti bejárás – a leggyakoribb és nagy fájlnál is takarékos
with open("nevsor.txt", encoding="utf-8") as f:
    egesz = f.read()
print(repr(egesz))

with open("nevsor.txt", encoding="utf-8") as f:
    for sor in f:
        print("Szia,", sor.strip() + "!")

A repr() megmutatja a láthatatlan karaktereket is: minden sor végén ott a \n sorvége-jel. Ezért a beolvasott sorokat általában a strip() metódussal tisztítjuk meg – különben a print saját sortörésével együtt üres sorok keletkeznének.

Mit ír ki?

sor = "alma\n"
print(len(sor), len(sor.strip()))
  1. 4 4
  2. 5 4
  3. 6 4

A \n egyetlen karakter (a sorvége-jel), így a szöveg 5 hosszú. A strip() levágja a szöveg elején és végén álló szóközöket, tabulátorokat és sortöréseket.

4. fejezet

Írás és hozzáfűzés

Íráshoz "w" vagy "a" módban nyitjuk meg a fájlt. A f.write(szoveg) pontosan azt írja ki, amit kap – sortörést nem tesz a végére, azt nekünk kell ("\n"). Kényelmesebb lehet a print(…, file=f), amely ugyanúgy működik, mint a képernyőre írás, csak a fájlba ír.

gyumolcsok = ["alma", "körte", "szilva"]
with open("lista.txt", "w", encoding="utf-8") as f:
    f.write("Bevásárlólista\n")
    for i, gy in enumerate(gyumolcsok, start=1):
        print(f"{i}. {gy}", file=f)

with open("lista.txt", encoding="utf-8") as f:
    print(f.read())

Futtasd, majd kattints a Munkakönyvtárban a lista.txt-re. Futtasd újra: a tartalom ugyanaz marad, mert a "w" mód minden megnyitáskor kiüríti a fájlt.

Hozzáfűzés

A "a" mód a meglévő tartalom végére ír. Tipikus használat a napló (log): minden futás egy új sort tesz a végére. Futtasd többször egymás után, és nézd meg a naplo.txt-t.

import random

eredmeny = random.randint(1, 6)
with open("naplo.txt", "a", encoding="utf-8") as f:
    f.write(f"dobás: {eredmeny}\n")

with open("naplo.txt", encoding="utf-8") as f:
    sorok = f.readlines()
print(f"Eddig {len(sorok)} dobás van a naplóban. Az utolsó: {sorok[-1].strip()}")

A „w” mód nem kérdez. Ha egy létező fájlt "w" módban nyitsz meg, a tartalma azonnal elvész – még akkor is, ha végül semmit sem írsz bele. Ha egy fájlt bővíteni akarsz, "a" kell; ha módosítani, akkor előbb olvasd be, módosítsd a memóriában, és utána írd vissza.

Mit ír ki?

for i in range(3):
    with open("proba.txt", "w", encoding="utf-8") as f:
        f.write(f"{i}. sor\n")

with open("proba.txt", encoding="utf-8") as f:
    print(len(f.readlines()))
  1. 1
  2. 3
  3. 0

A "w" mód minden megnyitáskor kiüríti a fájlt, így a három kör közül csak az utolsó írása marad meg: egyetlen sor (2. sor). "a" módban három sor lenne a fájlban.

5. fejezet

Táblázatos adatok: CSV

A CSV (comma-separated values) egyszerű táblázatformátum: minden sor egy rekord, a mezőket egy elválasztójel tagolja. A magyar Excel pontosvesszőt használ (mert a vessző nálunk tizedesjel), ezért gyakran ; az elválasztó. Az első sor sokszor a fejléc (az oszlopok neve).

Kézzel: split()

osszeg = 0
db = 0
with open("diakok.csv", encoding="utf-8") as f:
    fejlec = f.readline()
    for sor in f:
        nev, osztaly, pont = sor.strip().split(";")
        osszeg += int(pont)
        db += 1
        if int(pont) >= 80:
            print(f"{nev} ({osztaly}): {pont} pont – kiváló")
print(f"Átlag: {osszeg / db:.1f} pont")

A fejlécet egy readline() „elnyeli”, így a ciklus már csak az adatsorokat kapja. A split(";") a sort mezőkre bontja, a kicsomagolás pedig rögtön nevet ad nekik. Ne feledd: a fájlból beolvasott szám is szöveg, számoláshoz int() kell.

A csv modullal

A csv modul kezeli a bonyolultabb eseteket is (pl. idézőjelek közé tett mezőt, amelyben elválasztójel van). A DictReader minden sort szótárként ad vissza, a fejléc neveivel kulcsként; a writer pedig listákból készít sorokat.

import csv

with open("diakok.csv", encoding="utf-8", newline="") as f:
    olvaso = csv.DictReader(f, delimiter=";")
    diakok = list(olvaso)
print(diakok[0])

with open("kivalok.csv", "w", encoding="utf-8", newline="") as f:
    iro = csv.writer(f, delimiter=";")
    iro.writerow(["nev", "pont"])
    for d in diakok:
        if int(d["pont"]) >= 80:
            iro.writerow([d["nev"], d["pont"]])
print("Elkészült a kivalok.csv.")

A newline="" a csv modul ajánlása: így a modul maga kezeli a sorvégeket, és Windows alatt sem keletkeznek üres sorok.

6. fejezet

Szerkezetes adatok: JSON

A JSON szöveges formátum, amely a Python szótáraihoz és listáihoz nagyon hasonló szerkezetet ír le – ezért kiválóan alkalmas arra, hogy egy program teljes adatállapotát (beállítások, pontszámok, egy játékos mentése) elmentsük és visszatöltsük. A webes szolgáltatások is jellemzően JSON-ban adnak adatot.

import json

with open("pontok.json", encoding="utf-8") as f:
    pontok = json.load(f)
print("Betöltve:", pontok)

pontok["Dóra"] = 1450
legjobb = max(pontok, key=pontok.get)
print("A legjobb:", legjobb, pontok[legjobb])

with open("pontok.json", "w", encoding="utf-8") as f:
    json.dump(pontok, f, ensure_ascii=False, indent=2)
print("Elmentve.")
  • json.load(f) beolvassa a fájlt, és Python-szótárat/listát ad vissza; json.dump(adat, f) kiírja.
  • Az ensure_ascii=False miatt az ékezetes betűk olvashatók maradnak a fájlban, az indent=2 pedig tagolja a kimenetet.
  • A max(pontok, key=pontok.get) azt a kulcsot adja, amelyhez a legnagyobb érték tartozik.

A JSON-ban a szövegek mindig kettős idézőjelesek, a logikai értékek true/false, az „üres” érték null (Pythonban True, False, None). A tuple-ből JSON-tömb lesz (visszaolvasva tehát lista), a halmazt viszont a json nem tudja kiírni (TypeError) – előbb listává kell alakítani. Fontos az is, hogy a JSON-ban a szótár kulcsa mindig szöveg: a {1: "a"} kiírva {"1": "a"}, visszatöltve tehát "1" kulcs lesz.

7. fejezet

Ha valami nem sikerül: hibakezelés

Fájlkezelésnél sok minden elromolhat, ami nem a program hibája: a fájl nem létezik, nincs jogosultság hozzá, hibás benne egy adat. Ilyenkor a program kivételt (exception) kap. A try–except szerkezettel a kivételt el tudjuk kapni, és értelmes üzenetet adhatunk összeomlás helyett.

fajlnev = input("Melyik fájlt nyissam meg? ")
try:
    with open(fajlnev, encoding="utf-8") as f:
        sorok = f.readlines()
except FileNotFoundError:
    print(f"Nincs ilyen fájl: {fajlnev}")
else:
    osszeg = 0
    hibas = 0
    for sor in sorok:
        try:
            osszeg += int(sor)
        except ValueError:
            hibas += 1
    print(f"Összeg: {osszeg}, hibás sor: {hibas}")

Futtasd kétszer: egyszer a nincs.txt, egyszer a szamok.txt névvel. A try blokk figyelt módon fut; ha benne a megnevezett hiba történik, a megfelelő except ág veszi át a vezérlést. Az else ág akkor fut, ha nem volt hiba.

HibaMikor fordul elő?
FileNotFoundErrornincs ilyen fájl (elírt név, rossz mappa, hiányzó kiterjesztés)
PermissionErrornincs jogosultság (pl. írásvédett vagy más program által zárolt fájl)
UnicodeDecodeErrora fájl nem a megadott kódolású (pl. régi, nem UTF-8 fájl)
ValueErrora beolvasott szöveg nem alakítható számmá
json.JSONDecodeErrorhibás JSON-tartalom

Mindig konkrét hibát kapj el (except FileNotFoundError:), ne az összeset (except:). A mindent elkapó ág a programozási hibákat (elírt változónév, rossz index) is elrejti, és nagyon nehéz lesz megtalálni őket.

8. fejezet

Kitekintés: bináris fájlok

Bináris módban ("rb", "wb") nincs karakterkódolás: a fájl nyers bájtjait kapjuk meg egy bytes értékként. A képek, hangok formátumát a fájl elején álló „varázsszám” (fejléc) azonosítja – a PNG-képek például mindig ugyanazzal a 8 bájttal kezdődnek:

with open("kep.png", "rb") as f:
    adat = f.read()
print("Méret:", len(adat), "bájt")
print("Az első 8 bájt:", adat[:8])
print("PNG-kép?", adat[:8] == b"\x89PNG\r\n\x1a\n")
szelesseg = int.from_bytes(adat[16:20], "big")
magassag = int.from_bytes(adat[20:24], "big")
print(f"Képméret: {szelesseg} × {magassag} képpont")

A bájtok értelmezéséhez ismerni kell a formátum leírását – a PNG-nél a szélesség és a magasság a 16-os indextől (a 17. bájttól) kezdve, 4-4 bájton áll. A gyakorlatban képekhez kész könyvtárat (pl. Pillow) használunk; ez már túlmutat ezen a tananyagon.

9. fejezet

Gyakorló feladatok

A feladatok a munkakönyvtár fájljaival dolgoznak (a kódpanel alatt megnézheted őket). Ahol a programnak fájlt kell írnia, a gép a létrejött fájl tartalmát is összeveti az elvárttal.

Sorok számozása

Olvasd be a vers.txt fájlt (egy rövid, négysoros szöveg, a versszakok között üres sorral), és írd ki a sorait sorszámmal ellátva. Az üres sorokat ne számozd, hanem hagyd ki.

1. Reggel köd ült a völgyön,
2. délre kisütött a nap.
3. Este a szél feltámadt,
4. és a fák közt suttogott.
szam = 0
with open("vers.txt", encoding="utf-8") as f:
    for sor in f:
        sor = sor.strip()
        if sor != "":
            szam += 1
            print(f"{szam}. {sor}")
Tipp

for sor in f:, a strip()-elt sorral dolgozz. A számláló csak a nem üres soroknál nő.

Mérések átlaga

A meresek.txt soronként egy valós számot (hőmérsékletet) tartalmaz. Írd ki a mérések számát, a legkisebbet, a legnagyobbat és az átlagot egy tizedesjegyre.

Mérések: 6
Legkisebb: -2.5
Legnagyobb: 11.0
Átlag: 4.2
ertekek = []
with open("meresek.txt", encoding="utf-8") as f:
    for sor in f:
        ertekek.append(float(sor))
print("Mérések:", len(ertekek))
print("Legkisebb:", min(ertekek))
print("Legnagyobb:", max(ertekek))
print(f"Átlag: {sum(ertekek) / len(ertekek):.1f}")
Tipp

Gyűjtsd a float(sor) értékeket egy listába (a float() a sorvégi \n-t elviseli), utána jöhet a min, max, sum, len.

Bevásárlólista mentése

Olvass be tételeket üres sorig, és írd őket a lista.txt fájlba, soronként egyet, sorszámmal. A végén írd ki a képernyőre: 3 tétel mentve.

Tétel: kenyér
Tétel: tej
Tétel: alma
Tétel: 
3 tétel mentve.

lista.txt:
1. kenyér
2. tej
3. alma
tetelek = []
tetel = input("Tétel: ")
while tetel != "":
    tetelek.append(tetel)
    tetel = input("Tétel: ")
with open("lista.txt", "w", encoding="utf-8") as f:
    for i, t in enumerate(tetelek, start=1):
        f.write(f"{i}. {t}\n")
print(f"{len(tetelek)} tétel mentve.")
Tipp

Előbb gyűjtsd a tételeket listába, utána egyszer nyisd meg a fájlt "w" módban. A write() nem tesz sortörést – írd hozzá a "\n"-t (vagy használd: print(…, file=f)).

Átlag fölött

Az eredmenyek.csv fejléces, pontosvesszővel tagolt fájl (nev;pont). Írd ki az átlagpontszámot egy tizedesre, majd azok nevét (a fájl sorrendjében), akik az átlag fölött teljesítettek.

Átlag: 68.5
Átlag fölött:
Kiss Anna
Tóth Csilla
Szabó Dénes
nevek = []
pontok = []
with open("eredmenyek.csv", encoding="utf-8") as f:
    f.readline()
    for sor in f:
        nev, pont = sor.strip().split(";")
        nevek.append(nev)
        pontok.append(int(pont))
atlag = sum(pontok) / len(pontok)
print(f"Átlag: {atlag:.1f}")
print("Átlag fölött:")
for i in range(len(nevek)):
    if pontok[i] > atlag:
        print(nevek[i])
Tipp

Két menet kell: előbb az átlag (ehhez minden pont kell), utána a válogatás. Ezért tárold a neveket és a pontokat egy-egy listában (vagy egy szótárban). A fejlécet egy readline() nyeli el.

Ranglista JSON-ban

A ranglista.json egy szótár: név → pontszám. Kérj be egy nevet és egy pontszámot. Ha a név új, vedd fel; ha már szerepel, csak akkor írd felül, ha az új pontszám nagyobb. Mentsd vissza a fájlt (ensure_ascii=False, indent=2), majd írd ki a ranglistát pontszám szerint csökkenő sorrendben, sorszámmal.

Név: Emese
Pont: 1500
1. Emese – 1500
2. Csilla – 1325
3. Anna – 1200
4. Bence – 980
import json

with open("ranglista.json", encoding="utf-8") as f:
    pontok = json.load(f)
nev = input("Név: ")
pont = int(input("Pont: "))
if nev not in pontok or pont > pontok[nev]:
    pontok[nev] = pont
with open("ranglista.json", "w", encoding="utf-8") as f:
    json.dump(pontok, f, ensure_ascii=False, indent=2)
helyezes = 1
for n in sorted(pontok, key=pontok.get, reverse=True):
    print(f"{helyezes}. {n} – {pontok[n]}")
    helyezes += 1
Tipp

A feltétel: if nev not in pontok or pont > pontok[nev]: – a rövidzár miatt a pontok[nev] csak létező névnél értékelődik ki. Csökkenő rendezés érték szerint: sorted(pontok, key=pontok.get, reverse=True).

Biztonságos összegzés

Kérj be egy fájlnevet. Ha a fájl nem létezik, írd ki: Nincs ilyen fájl: …. Ha létezik, adja össze a soraiban álló egész számokat; a számmá nem alakítható sorokat hagyd ki, és számold meg őket. A végén: Összeg: 49, kihagyott sor: 1.

nev = input("Fájlnév: ")
try:
    with open(nev, encoding="utf-8") as f:
        sorok = f.readlines()
except FileNotFoundError:
    print("Nincs ilyen fájl:", nev)
else:
    osszeg = 0
    kihagyott = 0
    for sor in sorok:
        try:
            osszeg += int(sor)
        except ValueError:
            kihagyott += 1
    print(f"Összeg: {osszeg}, kihagyott sor: {kihagyott}")
Tipp

Két külön try kell: egy a megnyitás köré (FileNotFoundError), egy az int()-es átalakítás köré a cikluson belül (ValueError).

10. fejezet

Összefoglalás

EszközLényeg
with open(nev, mod, encoding="utf-8") as f:megnyitás; a blokk végén automatikus bezárás
"r", "w", "a"olvasás; írás (kiürít!); hozzáfűzés
for sor in f:, sor.strip()soronkénti olvasás, a sorvége-jel levágása
f.write(s), print(…, file=f)írás; a write nem tesz sortörést
split(";"), csvtáblázatos (CSV) adatok
json.load, json.dumpszótárak és listák mentése, betöltése
try / except / elsekonkrét hibák (FileNotFoundError, ValueError) kezelése

Gyakorold tovább: írj egy jegyzetfüzet-programot: induláskor töltse be a jegyzetek.json-t (ha nincs, üres listával induljon), egy menüből lehessen jegyzetet felvenni, listázni és törölni, kilépéskor pedig mentse vissza a fájlt.