Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Python
Code

Profi hibakezelés Pythonban

by
Difficulty:IntermediateLength:MediumLanguages:

Hungarian (Magyar) translation by Peter Matyus-Jarai (you can also view the original English article)

Ebből a tutorialból megtudhatod, hogyan kezelhetsz hibás helyzeteket Pythonban, a teljes rendszer szemszögéből. A hibakezelés a tervezés egy kritikus pontja, és egészen az alacsony szintű megvalósításoktól (néha hardver szinten) a végfelhasználókig szükség van rá. Ha nincs egy következetes időszerű stratégiád, a rendszered megbízhatatlan lesz, a felhasználói élmény gyenge, és sok debuggolással és hibakereséssel kell megbírkóznod.

A siker kulcsa, hogy tisztában legyünk ezekkel a kapcsolódó aspektusokkal, világosan mindet végiggondolva és olyan megoldást adjunk, ami érint minden kérdéses területet.

Státusz kódok kontra Kivételek

Két fő hibakezelő modell van: a státusz kódok és a kivétel kezelés. Státusz kódokat bármilyen programozási nyelvben használhatunk. A kivételek használatához a nyelv támogatása szükséges.

Python nyelv támogatja a kivételeket. A Python és a standard könyvtára bőségesen el van látva kivételekkel pl. IO hibákat jelez, nullával való osztást, indexelés tartomány túllépést és néhány nem jellemző esetben is, mint pl. az iteráció vége esetén (bár ez egy rejtett dolog). A legtöbb függvénykönyvtár így működik és kivételeket dob.

Ez azt jelenti, hogy a kódodnak le kell tudnia kezelni a kivételeket, amiket a Python és könyvtárai dobnak, és ezen felül te is dobhatsz kivételeket a saját kódodban, amikor szükséges, és nem kell a státusz kódoktól függnöd.

Gyors példa

Mielőtt beleássuk magunkat a Python kivételkezelés és hibakezelés praktikáinak mélységeibe, lássunk néhány kivételkezelést a gyakorlatban:

Ez a h() függvény kimenete:

Python kivételek

A Python kivételek osztály hierarchiába rendezett objektumok.

Itt van a teljes hierarchia:

Van néhány speciális kivétel, amelyek közvetlenül a BaseException osztályból származnak, mint pl. SystemExit, KeyboardInterrupt és GeneratorExit. Az Exception osztály az alap-ős osztálya a StopIterationStandardError és Warning osztályoknak. Az összes standard hiba a StandardError osztályból származik.

Amikor kivételt dobsz, vagy a függvény amit hívtál kivételt dob, a normál kód futtatása megáll, és a kivétel elkezd felfelé terjedni a hívásokon (call stack), egészen addig, amíg egy megfelelő kivétel kezelő el nem kapja. Ha nincs ilyen kivétel kezelő, ami elkapná, a folyamat (pontosabban az adott szál) befejeződik egy el nem kapott kivétel üzenettel.

Kivételek dobása

A kivételek dobása nagyon egyszerű. A raise kulcsszót kell használni, hogy egy olyan objektumot dobjunk, ami az Exception osztály gyermeke. Ez lehet konkrétan az Exception osztály maga, vagy az alap kivételek közül egy, pl. RuntimeError, vagy egy gyermek osztály, amit az Exception osztályból származtattál. Az alábbi kódrészlet mindegyik esetet bemutatja:

Kivételek elkapása

Az except záradékban lehet kivételeket elkapni, ahogy a példában is láthattuk. Amikor elkapsz egy kivételt, három lehetőséged van:

  • Csöndben elhallgatod, elfeded (kezeled a hibát és tovább fut).
  • Csinálsz valamit, mint pl. logolás, de ha újradobod ugyanazt a kivételt, akkor magasabb szinteken lehet kezelni.
  • Másféle kivételt dobsz az eredeti helyett.

Kivétel elhallgatása

Ha tudod hogyan kell kezelni és teljesen helyreállítani egy kivétel okozta problémát, akkor el kellene fedni.

Például vegyük hogy bemenetként egy file tartalmat kapsz, ami különféle formátumú lehet (JSON, YAML), és megpróbálhatod parsolni különféle feldolgozókkal. Ha a JSON feldolgozó egy olyan kivételt dob hogy a file nem érvényes JSON file, akkor elfedheted és kipróbálhatod a YAML feldolgozót arra a file-ra. Ha a YAML feldolgozó sem jár sikerrel, akkor hagyjuk a kivételt felterjeszteni.

Megjegyezzük,hogy más féle kivételek (pl. file nem található vagy nincs olvasási jogosultság) felterjesztődnek és nem fogja a specifikusan beállított kivétel záradék elkapni. Ez egy jó irányelv abban az esetben, amikor a YAML feldolgozót csak akkor akarod kipróbálni, ha a JSON feldolgozó nem járt sikerrel valamilyen JSON kódolási probléma miatt.

Ha minden kivételt kezelni szeretnél, akkor használd az except Exteption záradékot. Például:

Az as e kódot használva hozzákötöd a kivétel objektumot az e nevű változóhoz, ami a záradékban elérhető lesz.

Újra dobjuk ugyanazt a kivételt

Az újra dobáshoz csak adjuk hozzá a kivétel kezelésen belül a raise parancsot paraméterek nélkül. Ezzel helyileg tudod kezelni a problémát, de tovább dobva fentebbi szintek is értesülnek erről és kezelhetik. Itt az invoke_function() függvény kiírja a kivétel típusát a console-ra és újra dobja a kivételt.

Különböző kivételek dobása

Van néhány eset, amikor másféle kivételeket is dobni szeretnél. Néha szeretnéd csoportosítani az alacsony szintű kivételeket egyetlen kategóriába, amit egységesen kezelnél egy magasabb szintű kódban. Más esetben, a kivételt a felhasználó szintjére kell vinni, és néhány alkalmazás specifikus információt is meg kell jeleníteni.

Finally blokk

Néha szeretnénk azt biztosítani, hogy egy tisztító kód lefusson akkor is, ha kivétel történt valamikor a program futása során. Például van egy adatbázis kapcsolatunk, amit szeretnék bezárni ha készen vagyunk. Ez egy rossz mód:

Ha a query() függvény kivételd dob akkor a close_db_connection() sosem fog lefutni és a DB kapcsolat nyitva marad. A finally blokk mindig lefut miután az összes kivételkezelés is lefutott. Itt van, hogyan kell ezt jól csinálni:

Az open_db_connection() hívása nem biztos hogy egy kapcsolatot ad vissza, vagy mondjuk kivételt fog dobni. Ebben az esetben nincs szükség a DB kapcsolat lezárására.

Amikor a finally blokkot használjuk, óvatosnak kell lennünk, hogy ne dobjunk kivételt ezen belül, mert az eredeti kivételt elfedi.

Context manager protokoll

A context manager protokoll egy újabb mód arra, hogyan lehet az erőforrásokat mint pl. fileok, DB kapcsolatok, körülvenni a tiszta kódban, ami automatikusan végrehajtódik még ha kivételek is történtek. Try-finally blokkok helyett használhatjuk a with kifejezést. Itt van egy példa:

Ha a process() függvény kivételt dob, a file rendesen le lesz zárva azonnal, amikor a with blockból kilép a program, akár volt a kivétel kezelve akár nem.

Loggolás

A logolás eléggé alapvető követlemény a nem triviális sokáig futó rendszerekben. Különösen hasznos web alkalmazás esetén amikor a kivételeket általánosan kezelheted: Csak logold a kivételt és adj vissza egy hibaüzenetet a hívónak.

Amikor logolunk, hasznos a kivétel típusát, a hibaüzenetet és a híváslistát is logolni. Az összes információ elérhető a sys.exc_info objekumon keresztül, de használhatod a logger.exception() függvényt is. kivételkezelőnek, a Python logoló rendszere kiolvas minden fontos információt a számodra.

Ez a legjobb minta, amit javaslok:

Ha követed ezt a mintát, akkor (feltéve ha a loggolást jól állítottad be) mindegy mi történik, elég jó bejegyzéseid lesznek a logokban arról, mi volt rossz, és ki fogod tudni javítani a feladatot.

Ha újra dobod a kivételt, biztosnak kell lenned, hogy nem logolod újra és újra ugyanazt a kivételt különbözö szinteken. Ez egy felesleges dolog, és össze is kavarhat, mert arra gondolhatsz hogy többször is előfordult az eset, de valójában egy problémát logolt többször a rendszer.

A legegyszerűbb mód az, ha hagyunk minden kivételt a végéig elmenni (kivéve ha biztosan lehet kezelni hamarabb) és a logolást az alkalmazás felső szintjének közelében végezzük el.

Őrszem

A logolás egy képesség. A legtöbb implementáció log fileokat használ. De a nagy teljesítményű elosztott rendszerek esetén, száz, ezer vagy több szerverrel felszerelve, nem biztos hogy ez a legjobb megoldás.

Ahhoz, hogy a kivételeket nyilvántartsd az egész infrastruktúrádban, egy olyan szolgáltatás, mint a sentry szuper hasznos. Központosítja a kivételek riportjait, és a hívások listáján kívül a környezet változóinak állapotát is tárolja, a változók értékeit a kivétel dobásakor. Nagyon szép felületet is nyújt vezérlőpulttal, riportokkal és az üzenetek szűrésével, akár több projekt esetén is. Nyílt kódú így saját szervert is futtathatsz vagy feliratkozhatsz a hostolt verzióra is.

Foglalkozzunk az átmeneti hibákkal

Néhány hiba csak átmeneti, különösen amikor elosztott rendszerekkel foglalkozunk. Egy rendszer, ami kiakad az első hibánál nem igazán hasznos.

Ha a kódod néhány távoli rendszert ér el, ami épp nem válaszol, a hagyományos megoldás a timeout, de néha nincs minden rendszerben timeout kezelés. Timeout-ot nem mindig könnyű kalibrálni ahogy a feltételek változnak.

Egy másik megközelítés az, hogy gyorsan ismerjük el a hibát, és próbáljuk újra. Ennek az az előnye, hogy ha a célgép gyorsan válaszol, akkor nem kell sok időt tölteni az altató feltétellel, és azonnal lehet reagálni. De ha hibára fut, többször újrapróbálhatod a kapcsolatot, amíg úgy nem döntesz, hogy valóban elérhetetlen, és dobsz egy kivételt. A következő részben bemutatok egy decorator-t, ami ezt megoldja számodra.

Hasznos díszítők

Két díszítő mintát mutatunk be, ami segít a hibakezelésben, az egyik a @log_error, ami egy kivételt logol majd újra dobja, a másik a @retry díszítő, ami megpróbálja néhányszor újra hívni a függvényt.

Hiba loggoló

Itt van egy egyszerű implementáció. A decorator egy loggoló objektumot vár. Amikor egy függvényt díszít és a függvényt meghívják, egy try-except blokkal veszi körül, és ha kivétel történik, logolni fogja és végül újradobja a kivételt.

Itt van, hogyan lehet használni:

Újra próbáló (retrier)

Egy nagyon jó implementációt láthatunk a @retry decorator-hoz.

Konklúzió

A hibakezelés kritikus fontosságú mind a felhasználóknak mind a fejlesztőknek. A Python nyelv és az alapkönyvtár nagyon jó támogatást nyújt a kivétel alapú hibakezeléshez. Ha gondosan követjük az ajánlásokat, felfedezheted ezt a gyakran elfelejtett területet.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.