Informatika2-2015/Eloadas 5 Python-5 Meg referenciak Hibakezeles

A MathWikiből
A lap korábbi változatát látod, amilyen Csirke (vitalap | szerkesztései) 2015. március 16., 01:21-kor történt szerkesztése után volt.

Tartalomjegyzék

Referenciák még kicsit

Jó paraméter módosítás

Nézzünk egy konkrét példát amikor érdemes kihasználni az objektumok változtathatóságát, hogy referenciákon keresztül férek hozzájuk. A következő példában egy adatbázis bejelentkezését kezelem, és van egy szótáram ami tárolja hogy melyik felhasználónak hány sikertelen belépése volt. Itt egy kódvázlat ami követi az eddigi tanácsaimat, és nem módosít bele paraméterként kapott változóba:

link

Azt láthatjuk, hogy a 8-12 lépéseknél két másolat is van az adatokról, amik nem, vagy csak alig térnek el egymástól. Elképzelhető lenne, ha elég nagy a rendszer, hogy ez a szótár nem csak 3 felhasználót tárol, hanem több ezret. Ezzel egy akár néhány megabyte-os új objektumot hozok létre a memóriában. (Lehet még optimalizálni a kódot, hogy csak akkor másolja le ha tényleg belemódosít, de az most mindegy.)

Azonban, ha nem lesz szükség az eredeti változatra később (és ebben az esetben miért lenne), akkor módosíthatom a paramétert:

link

Amit itt meg kell figyelni, hogy:

  • Le van dokumentálva hogy módosítom a paramétert. (Ha valamelyik felhasználó nem szeretné hogy módosuljon az objektuma, még mindig adhat csak egy másolatot át.)
  • Nincs visszatérési érték. Ez általános módszer annak a jelzésére hogy nem egy módosított változat készült, hanem az eredeti objektum módosult.

Általánosságban pont ezért módosíthatóak az objektumok, mert nagy adathalmazoknál nem engedhető meg mindig másolat készítése. A CloudCoder-ben írt és hasonló gyakorló jellegű feladatok megoldásánál továbbra is azt ajánlom hogy egyik függvényetek se módosítson a paraméterein, hanem csak ha biztosak vagytok benne hogy kell, azt kezeljétek speciális esetként.

Referencia mint mutató

Az a koncepció, hogy az objektumok léteznek a memóriában, attól függetlenül hogy milyen néven hívjuk őket, és a változók csak referenciák amik mutatnak ezekre az objektumokra, segíthet értelmezni a következő kódot:

link

Egészen a 7-es lépésig az a felfogás, hogy ezek a listák egymáson belül vannak, működik teljesen jól. Print-elve is így néz ki, 3 lista egymáson belül. Azonban az utolsó két sor értelmezéséhez el kell felejtenünk ezt az elképzelést. Ez már csak akkor értelmezhető, ha a listáimat egy-egy gráf pontjaiként értelmezem, amik között irányított élek vannak. Egy-egy referencia mutathat egy változóból, vagy egy lista vagy szótár egy eleméből, egy tetszőleges objektumra.

Ez a felfogás mutatja, hogy az ilyen egymásba zárójelezett listák pl. egy faszerkezetet jelképeznek gráfként nézve. Ennek megfelelően manipulálhatóak is:

link

vagy

link

Egyelőre nem kell ezt ilyen műveletekre felhasználnotok, de jó ha elkezdtek átszokni erre a gondolkodásra, mert akkor könnyebb lesz megérteni a programok működését sok esetben.

Hibakezelés

Alap python viselkedés

Nézzük ezt a (hibás) kódot:

link

Ha futtatom ezt a kódot a parancssorból, akkor a következő üzenetet kapom a python-tól:

Traceback (most recent call last):
  File "D:\prog\Info2\teszt\hiba.py", line 8, in <module>
    l2 = listaoszt([5, 10, 15], 0)
  File "D:\prog\Info2\teszt\hiba.py", line 6, in listaoszt
    l[i] = oszt(l[i], b)
  File "D:\prog\Info2\teszt\hiba.py", line 2, in oszt
    return a / b
ZeroDivisionError: integer division or modulo by zero

Már láthattatok ilyesmit ha futtatok a gépeteken kódot (akár Spyder-rel), a CloudCoder is ennek az üzenetnek a végét adja vissza hiba esetén.

Itt lehet látni, hogy kiírja az összes függvényt ami éppen fut a hiba pillanatában, majd a hiba típusát. Ez általában a programozó számára elég sok információ, hogy legalábbis el tudja kezdeni keresni a hiba pontos forrását. Azonban ha a programunknak felhasználója van, számára ez biztos nem barátságos hibaüzenet. Nézzük meg mit tehetünk ezzel.

Hibák típusai

Alapvetően megkülönböztethetünk két fajta hibát ami előfordulhat a programban. Az egyik eset ha a programozó vétett hibát, és a program nem működik jól. A második eset ha a program felhasználója rosszul próbálja használni a programot. Utóbbira példa lehet ha a fentihez hasonlóan egy számológép programban nullával próbál osztani, vagy akár az is ilyen eset ha olyan bejelentkezési nevet/jelszavat ad meg ami nem helyes.

Az első esettel kis programok esetén, akár néhány száz felhasználóig, ha még a felhasználók tudnak közvetlenül szólni a fejlesztőnek, nem kell foglalkoznunk különösebben előre. Úgyis a programozónak kell majd javítani a hibát, így nem baj ha a python "programozó nyelven" mondja meg hogy mi a hiba. A felhasználó szempontjából az a megoldás hogy szól a programozónak hogy "ezt próbáltam csinálni, és akkor a program ezt a furcsa üzenetet adta vissza", és remélhetőleg ez alapján a programozó meg tudja oldani a hibát.

Utóbbi esetben viszont a felhasználó számára az lenne a jó, ha megmondaná neki a program hogy hogy használja rosszul, és ő ki tudná javítani, anélkül hogy szólnia kéne bárki másnak. (És a programozónak is jobb ha nem őt zaklatják minden ilyen esetben, hanem a program elmagyarázza a bajt. Összességében kevesebb munka.) Az ilyen esetet már néhány soros, egyetemi project-ekben használt, egyszerű programoknál is érdemes kezelni ahol nem nagy gond. Ha már legalább egy másik ember elképzelhető hogy használni fogja, vagy akár saját magunk néhány évvel később.

Egyszerű kezelés

Az ilyen problémák kezeléséhez nincsen feltétlenül szükség új technikákra. Lehet például így kezelni:

import datetime
import calendar
 
ev = int(raw_input("Szuletesunk eve? "))
honap = int(raw_input("Honapja? "))
nap = int(raw_input("Napja? "))
 
if (ev < datetime.MINYEAR or ev > datetime.MAXYEAR or
        honap < 0 or honap > 12):
    print "Hibas honap vagy ev!"
else:
    if nap < 0 or nap > calendar.monthrange(ev, honap)[1]:
        print "Hibas nap!"
    else:
        szulinap = datetime.date(ev, honap, nap)
 
        print szulinap

Láthatóan jelentősen nőtt a kód bonyolultsága. Ha nem probléma hogy a hiba hatására kilép a program/függvény akkor még valamivel lehet egyszerűsíteni így:

import datetime
import calendar
 
ev = int(raw_input("Szuletesunk eve? "))
honap = int(raw_input("Honapja? "))
nap = int(raw_input("Napja? "))
 
if (ev < datetime.MINYEAR or ev > datetime.MAXYEAR or
        honap < 0 or honap > 12):
    print "Hibas honap vagy ev!"
    exit()
 
if nap < 0 or nap > calendar.monthrange(ev, honap)[1]:
    print "Hibas nap!"
    exit()
 
szulinap = datetime.date(ev, honap, nap)
 
print szulinap

Így legalább nem mélyül a kód egyre bejlebb, de kevésbé látszik hogy a végén levő kód csak bizonyos esetekben fut le. Bár a bizonyos esetek az (elvileg ritka) hibás esetek, ezért ez annyira nem baj.

Viszont továbbra is olyan ellenőrzéseket végzünk, amiket már a python elvégezne magától is, és még nem is sikerült minden rossz esetet kezelnünk, kipróbálhatjuk mi történik, ha azt adjuk meg hónapnak, hogy "Február".

Kivételek

Ahhoz hogy megértsük hogy tudunk a python-nal együtt működni, és a beépített ellenőrzéseket a hasznunkra fordítani, meg kell értenünk a kivétel (exception) fogalmát. Amikor a python beépített könyvtárainak függvényei hibát találnak, akkor "emelnek egy kivételt" (raise an exception). A kivétel maga egy python objektum, ami tartalmazza valamilyen formátumban a "ValueError: month must be in 1..12" szerű hibaüzenetet. Amikor a kivétel fel van emelve, akkor a python alapértelmezésben kilép minden éppen futó függvényből, és kiírja a kivételben tárolt üzenetet, és minden függvényt amiből menet közben kilépett.

Azonban meg lehet állítani a kivétel emelkedését, a try-except parancs párral. Legegyszerűbb módon így:

import datetime
 
try:
    ev = int(raw_input("Szuletesunk eve? "))
    honap = int(raw_input("Honapja? "))
    nap = int(raw_input("Napja? "))
 
    szulinap = datetime.date(ev, honap, nap)
 
    print szulinap
except:
    print "Hibas datum!"

Az az except azt jelenti, hogy bármilyen hiba történik a try blokkon belül, egyből odaugrok az except blokk elejére, és végrehajtom az abban levő kódot.

Ha szeretnénk a kivételben levő üzenethez is hozzáférni, akkor el kell kapnunk a kivételt, ezt így kell:

import datetime
 
try:
    ev = int(raw_input("Szuletesunk eve? "))
    honap = int(raw_input("Honapja? "))
    nap = int(raw_input("Napja? "))
 
    szulinap = datetime.date(ev, honap, nap)
 
    print szulinap
except Exception as e:
    print "Hibas datum! Hibauzenet:"
    print e

Nézzünk egy kicsit bonyolultabb esetet, hogy jobban megérthessük az except finomságait. Kezdjük ezzel a hibakezelés nélküli kóddal:

import datetime
 
def datum_beolvas():
    ev = int(raw_input("Szuletesed eve? "))
    honap = int(raw_input("Honapja? "))
    nap = int(raw_input("Napja? "))
    return datetime.date(ev, honap, nap)
 
def nev_azonosito(nev, nevek):
    i = 1
    while i <= len(nevek):
        if nevek[i] == nev:
            return i
        i += 1
 
def szulinap_atir(nevek, szulinapok):
    nev = raw_input("Neved? ")
    azonosito = nev_azonosito(nev, nevek)
    szulinap = datum_beolvas()
    szulinapok[azonosito] = szulinap
 
nevek = ["nyuszi", "roka", "farkas"]
szulinapok = [datetime.date(1, 1, 1) for i in range(3)]
 
while True:
    szulinap_atir(nevek, szulinapok)
    print nevek
    print szulinapok
    # Kepzeljuk ide hogy fajlba is kiirja

Végső kód ami csak a felhasználói hibákat jelzi:

import datetime
 
def datum_beolvas():
    ev = int(raw_input("Szuletesed eve? "))
    honap = int(raw_input("Honapja? "))
    nap = int(raw_input("Napja? "))
    return datetime.date(ev, honap, nap)
 
def nev_azonosito(nev, nevek):
    for i in range(len(nevek)):
        if nevek[i] == nev:
            return i
 
def szulinap_atir(nevek, szulinapok):
    nev = raw_input("Neved? ")
    azonosito = nev_azonosito(nev, nevek)
    szulinap = datum_beolvas()
    szulinapok[azonosito] = szulinap
 
nevek = ["nyuszi", "roka", "farkas"]
szulinapok = [datetime.date(1, 1, 1) for i in range(3)]
 
while True:
    try:
        szulinap_atir(nevek, szulinapok)
        print nevek
        print szulinapok
    except ValueError as e:
        print "Sikertelen nev valtoztatas! Hibauzenet:"
        print e
    # Kepzeljuk ide hogy fajlba is kiirja
Személyes eszközök