# 10. hét: fájlkezelés

Czirkos Zoltán · 2025.09.06.

Szövegfájlok beolvasása. Formátumhibák kezelése.

Felkészülés a laborra:

- A fájlkezelésről szóló előadásrészlet áttekintése.
- A kivételkezelésről szóló előadás felelevenítése.

## 1. Szavak beolvasása és szűrése

Adott a következő fájl: szavak_50.txt. Ez ötven darab magyar szót tartalmaz. Mentsd le ezt a fájlt a programod mellé, mert ezzel kell dolgoznia!

Írj programot, amelyik az alábbi feladatokat végzi el:

- Beolvassa a fájl szavait egy listába. Ügyelj arra, hogy ne legyen sorvége jel a szavak végén! Legyen ez a `betoltes()` nevű függvény, amelynek paramétere a fájlnév, visszatérési értéke a szavak listája! Ellenőrizd a szavak számát, és magukat a szavakat is a beolvasás után!
- Rendezd sorba a listát a beépített rendező függvénnyel, `sorted()` vagy `.sort()`.
- Írd ki egy másik fájlba csak a `k` betűvel kezdődő szavakat! Legyen ennek a neve `szavak_kbetus.txt`, és legyen ugyanolyan a formátuma, soronként egy darab szó! Ellenőrizd a keletkező fájlt, pl. jegyzettömbbel! A fájlba írást végezze a `mentes()` nevű függvény, amelynek paraméterben lehet megadni a fájlnevet és a mentendő listát!

Ne felejtsd el sehol bezárni a fájlt! Használd a `.close()` függvényt, vagy `with` blokkot, ahogy az előadáson szerepelt.

> ### Karakterkódolások

> Előfordulhat (tipikusan Windowson), hogy a fájlt a Python nem olyan karakterkódolással olvassa be, ahogy kellene. Ilyenkor az ékezetes betűk helyett furcsa karakterek jelennek meg, pl. „árvíztűrő tükörfúrógép” helyett „árvíztûrõ” vagy „ĂĄrvĂztĹąrĹ”. Ha ilyet létsz, az `open()` függvénynek add meg paraméterként a karakterkódolást is. Ez a többi feladatra is érvényes:

>     open("szavak_50.txt", "rt", encoding="utf-8")

> A témáról többet a karakterkódolások oldalon olvashatsz. Ez nem vizsgaanyag, de érdemes tudni róla, mert gyakran okoz galibát.

> Azt is észreveheted, hogy a sorba rendezés is hibás: az ékezetes karakterrel kezdődő szavak a lista végére kerülnek. Ennek az az oka, hogy pl. a 'á' betű kódja az nagyobb, mint a 'z' betűjé.

>     print(ord('á'),ord('z'))
>     225 122

> Ezen úgy lehet segíteni, hogy egyrészt megadjuk azt a nyelvet, amin a sorbarendezés történik, ezt az ún. locale segítségével tehetjük meg, másrészt explicite kérjük a sorted függvényt, hogy a nyelvfüggő összehasonlító függvényt használja. Részletek a referenciában, többnyelvű program írásakor érdemes használni.

>     import locale

>     szavak=['alma','áfonya','banán','szőlő']
>     locale.setlocale(locale.LC_COLLATE, 'hu_HU') # így csak a sorrend vagy LC_ALL, akkor mindent magyar nyelv szerint.
>     print(*sorted(szavak))
>     print(*sorted(szavak, key= locale.strxfrm))

>     alma banán szőlő áfonya
>     áfonya alma banán szőlő

## 2. Metróvonalak

Adott két fájl: m1.txt és m2.txt. Ezek a budapesti 1-es és 2-es metró megállóinak neveit tartalmazzák.

- Írj függvényt, amely paraméterként a fájl nevét kapja, és visszaad egy listát, amelyik a megállók neveit tartalmazza!
- Írj függvényt, amelyik paraméterként két fájl nevét kapja, és meghatározza, hogy át lehet-e szállni az egyik metróvonalról a másikra! Ha igen, térjen vissza a megálló nevével, ha nem, akkor `None` értékkel!
- Alakítsd át a programot egy teljes parancssori alkalmazássá, amelynek két paramétere van ami a metrómegállók neveit tartalmazza! Ehhez szükséged lesz a parancssori paraméterek kezelésére.

Teszteld a programod további adatokkal! A másik két metróvonal: m3.txt és m4.txt. Az M1–M4 között nincs átszállási lehetőség, a többinél van.

## 3. ZH eredmények

A következő fájl egy régebbi évfolyam első kis ZH-n elért eredményeit tárolja: kzh_pontszam.txt. A fájlban a pontszámok ömlesztve vannak, minden sorban egy szám.

- Olvasd be a fájlban tárolt számokat egy listába! Ügyelj arra, hogy ezeket `int`-té kell alakítanod. Legyen ez a `beolvas()` függvény, amelynek paramétere a fájlnév, értéke a pontszámok sorozata!
- A legkisebb pontszám 0, a legnagyobb 10. Készítsd statisztikát: hány 0 pontos, hány 1 pontos, ... ZH lett! Tedd ezt a `stat()` nevű függvénybe, amely paraméterként az összes pontszámok kapja, visszatérési értékként pedig a statisztikát állítja elő! (Pontszámmal indexelhető lista, amely létszámokat tartalmaz.)
- Írd ki a statisztikát, pontszámok szerinti eloszlást a `stat_kiir()` függvényben, amely paraméterként a `[pontszám]→létszám listát kapja!` Írd ki azt is, hogy hány sikeres ZH lett, ahol a pontszám legalább 4! Hány %-a ez az évfolyamnak?
- Ügyelj itt is a fájl bezárására! Hívd meg a főprogramból a függvényeket, hogy megjelenjen az eredmény!

Ez kell legyen az eredmény:

     1 db  0 pontos
     3 db  1 pontos
    10 db  2 pontos
    11 db  3 pontos
     9 db  4 pontos
    12 db  5 pontos
    14 db  6 pontos
    41 db  7 pontos
    11 db  8 pontos
    18 db  9 pontos
    29 db 10 pontos
    Átment: 134 fő, 84.28%

Megoldás

A legegyszerűbb megoldás a fájl sorainak beolvasására a `for` ciklus, mert ez szövegfájlból eleve sorokat ad:

    with open(fajlnev) as f:
        for sor in f:
            szamok.append(int(sor))

A teljes program:

    def beolvas(fajlnev):
        """Beolvassa a pontszámokat, és int-ek listáját adja
        vissza, pl. [10, 7, 8, 9, 10, 7, 5, 3]."""
        szamok = []
        with open(fajlnev) as f:
            for sor in f:
                szamok.append(int(sor))
        return szamok

    def stat(pontszamok):
        """Pontszámok szerinti statisztikát készít, és ezt adja vissza,
        pl. [5 (darab 0 pontos), 7 (darab 1 pontos), ...]."""
        db = [0] * 11   # [0, 0, 0, ...]
        for p in pontszamok:
            db[p] += 1
        return db       # [5, 7, 3, ...]

    def stat_kiir(db):
        """Kiírja az előállított statisztikát. A db nevű lista
        indexei a pontszámok, értékei a létszámok."""
        for p in range(0, 10+1):
            print("{:2} db {:2} pontos, {}".format(db[p], p, "*" * db[p]))
        # hány fő írta meg: a létszámok összege.
        osszes = sum(db)
        # átment: a lista 4 pont fölötti részében a létszámok összege.
        atment = sum(db[4:])
        print("Átment: {} fő, {:.4}%".format(atment, atment/osszes * 100))

    def main():
        pontszamok = beolvas('kzh_pontszam.txt')
        db = stat(pontszamok)
        stat_kiir(db)

    main()

## 4. Szám vagy nem szám?

Tedd félre kicsit a pontszámos programot, később még lesz vele feladat!

Írj egy függvényt, amelyik paraméterként egy sztringet vesz át, és visszatérési értékében megmondja, hogy szám van benne vagy nem szám! Pl. `szam_e("123")` értéke `True`, `szam_e("almafa")` értéke viszont `False` kell legyen. Emlékezz vissza: ezt legegyszerűbben úgy lehet megoldani, hogy számmá konvertálod a kapott sztringet, és elkapod a kivételt, ha kell.

Megoldás

    def szam_e(s):
        try:
            int(s)
            return True
        except ValueError:
            return False

    def main():
        s = input("Írj be valamit: ")
        print("Szám." if szam_e(s) else "Nem szám.")

    main()

## 5. Hibás fájl

A kzh_pontszam_hibas.txt fájl ugyanazokat az adatokat tartalmazza, mint az előző – azzal a különbséggel, hogy ebben hibás sorok is vannak. Némelyik üres, máshol beficcen egy-egy szó vagy egy valós szám, ami nem való a helyes adatok közé.

A feladatod úgy módosítani az előző feladat beolvasó függvényt, hogy kihagyja ezeket a hibás sorokat, és csak a helyes adatokat adja vissza a listában!

Tipp

Egy sor beolvasásánál három eset lehetséges:

- Vége lett a fájlnak, nem lehetett sort beolvasni.
- Volt mit beolvasni, de az hibás, pl. üres sor, egy szó, vagy egy valós szám.
- Helyes pontszámot lehetett kapni a fájlból.

A lényeg, hogy a két lehetséges hibát el kell választani egymástól. Amikor a fájl végére értünk, akkor a `.readline()` üres sztringet ad – erre lehet ciklust építeni.

Megoldás

Fájl végét ellenőrző ciklussal:

    def beolvas(fajlnev):
        szamok = []
        with open(fajlnev) as f:
            while True:
                sor = f.readline()
                if sor == "":
                    break
                try:
                    szamok.append(int(sor))
                except ValueError:
                    pass
        return szamok

Ha `for` ciklussal kezeltük a fájl sorait, akkor eleve megállt a ciklus a fájl végén. A konverziós hibát le kell kezelni ilyenkor is:

    def beolvas(fajlnev):
        szamok = []
        with open(fajlnev) as f:
            for sor in f:
                try:
                    szamok.append(int(sor))
                except ValueError:
                    pass
        return szamok

## 6. Dolgozatok

A zheredmeny.txt fájl, ahogy a tartalmán is látszik, kitalált neveket, NEPTUN kódokat és dolgozatok eredményeit tárolja:

    DHSF7F:Fül Elek:24
    ZPD9PY:Szőke Barna:17
    LGNUKG:Metall Ica:3
    XMOO5D:Füle Imre:12
    ...

Minden sor egy dolgozat adatait tárolja: NEPTUN kód, név és pontszám; mindezek kettősponttal elválasztva.

- Definiálj osztályt, amelyik egy nevet, NEPTUN kódot és pontszámot tárol!
- Írj függvényt, amelyik paraméterként kapott nevű fájlból beolvassa ezeket az adatokat egy listába! Ügyelj itt is, hogy `int`-ként tárold a pontszámot.

Hozz létre egy második listát, amelyik ugyanazokat a dolgozat objektumokat tartalmazza, mint az előbbi! Nagyon figyelj arra, hogy ez mit jelent: nem kell beolvasni még egy fájlt, nem kell létrehozni új dolgozat objektumokat, hanem csak egy új listába be kell tenni ugyanazon dolgozatok referenciáit!

A következőképp tudod ellenőrizni, hogy ezt jól csináltad-e:

    lista1 = ...
    lista2 = ...

    print(lista1 is lista2)         # False, mert két külön listáról van szó

    print(lista1[0] is lista2[0])   # True, ugyanazok az objektumok vannak benne

    lista1[0].pontszam = 27         # Reklamált, változott a pontszáma

    print(lista2[0].pontszam == 27) # True, mert ugyanaz a dolgozat objektum

Rendezd ezek után a két listát; egyiket név szerint, másikat pontszám szerint! Írd ki az eredményt a képernyőre, ellenőrizd az adatokat!

## 7. ZH eredmények oszlopdiagramon

Dolgozz tovább a kis ZH pontszámos feladattal! Oldd meg teknőcgrafikával, hogy egy oszlopdiagramot kapj az eredményekről! Az x tengelyen a pontszámok vannak, az y tengely pedig a létszámot mutatja, hogy hányan értek el annyi pontot. Ehhez hasonló kell legyen az eredmény:

Megoldás

    import turtle

    def beolvas(fajlnev):
        """Beolvassa a pontszámokat, és int-ek listáját adja
        vissza, pl. [10, 7, 8, 9, 10, 7, 5, 3]."""
        szamok = []
        with open(fajlnev) as f:
            while True:
                try:
                    szamok.append(int(f.readline()))
                except:
                    break
        return szamok

    def stat(pontszamok):
        """Pontszámok szerinti statisztikát készít, és ezt adja vissza,
        pl. [5 (darab 0 pontos), 7 (darab 1 pontos), ...]."""
        db = [0] * 11   # [0, 0, 0, ...]
        for p in pontszamok:
            db[p] += 1
        return db       # [5, 7, 3, ...]

    def teglalap(w, h):
        turtle.begin_fill()
        turtle.forward(w)
        turtle.left(90)
        turtle.forward(h)
        turtle.left(90)
        turtle.forward(w)
        turtle.left(90)
        turtle.forward(h)
        turtle.left(90)
        turtle.end_fill()

    def stat_rajz(db):
        turtle.speed(0)
        turtle.hideturtle()
        turtle.up()
        turtle.goto(0, 0)
        turtle.down()
        for letszam in db:
            turtle.forward(5)
            teglalap(15, letszam * 5)
            turtle.forward(15 + 5)

    def main():
        pontszamok = beolvas('kzh_pontszam.txt')
        db = stat(pontszamok)
        stat_rajz(db)
        turtle.done()

    main()

Készíts a „dolgozatok” című feladat adataiból is grafikont, az előzőhöz hasonlóan!