Изключения

„ Програмиране с Python“, ФМИ

class LectorsException(Exception): pass
raise LectorsException("Стефан Кънев, Николай Бачийски, Димитър Димитров")

14.04.2008г.

Традицията повелява

Ето как най-често се справяме с грешките в нашите програми:

"""Модул за зимнината на Митьо Питона"""
import jars

ERROR = -1
SUCCESS = 0

def prepare_for_winter():
    jar = jars.Jar()
    if jar.clean() == jars.ERROR:
        print "Couldn't clean Mityo's jar!"
        return ERROR
    if jar.fill('python juice') == jars.ERROR:
        print "Couldn't fill Mityo's jar!"
        return ERROR
    if jar.close() == jars.ERROR:
        print "Couldn't close Mityo's jar!"
        return ERROR
    return SUCCESS

Традициите не са това…

Сега да опитаме с изключения:

"""Модул за зимнината на Митьо Питона"""
import jars

class MityoWinterError: pass

def prepare_for_winter():
    try:
        jar = jars.Jar()
        jar.clean()
        jar.fill('python juice')
        jar.close()
    except jars.Error, error:
        print "Shit happens"

Синтаксис и семантика

try:
    блок
except изключения:
    блок ако се случи някое от описаните изключения

…

except още изключения:
    блок ако се случи някое от описаните изключения
except:
    блок ако изключението не е хванато по-горе
finally:
    блок изпълнява се винаги
else:
    блок ако не е възникнала изключителна ситуация

Аз не (при|с)хващам

По подразбиране, при неприхванато изключение, Python спира изпълнението на програмата и отпечатва на стандартния изход описание на грешката и реда на извикване на функциите до момента на грешката.

bad.py:

l = [1, 2, 3]
def bad(): print l[3]
bad()
След изпълнение получаваме:
$ python bad.py
Traceback (most recent call last):
  File "bad.py", line 3, in <module>
    bad()
  File "bad.py", line 2, in bad
    def bad(): print l[3]
IndexError: list index out of range

Изключенията се използват активно от вградените средства в езика.

Започвам да (при|с)хващам

def distribute_over(beers):
    try:
        return 333/beers
    except ZeroDivisionError:
        return 0

Изключенията са класове, инстанции или низове, като последните не е препоръчително да се ползват.

>>> ZeroDivisionError
<type 'exceptions.ZeroDivisionError'>

Можем да прихванем и по-общ тип изключение (родителски клас):

def distribute_over2(beers):
    try:
        return 333/beers
    except ArithmeticError:
        return 0

Ето и доказателство:

>>> issubclass(ZeroDivisionError, ArithmeticError)
True

Тази практика е много логична, тъй като делението на нула е и аритметична грешка и когато прихващаме аритметичните грешки, би трябвало да хванем и делението на нула.

По-гъвкаво прихващане

finally

file = open('data.txt')
try:
    mymodule.load_info(file)
except IOError, data:
    print "Couldn't read from file: %s" % data
except (mymodule.BadDataError, mymodule.InternalError), data:
    print "Loading failed: %s" % data
else:
    print "Data loaded successfully from file."
finally:
    file.close()

Ако присъства, finally стои винаги най-отдолу.

Пораждане на изключителни ситуации — низове (1)

LonelyError = "Stefan is lonely!"
def make_lonely(): raise LonelyError
try:
    make_lonely()
except LonelyError:
    print LonelyError

Към можем низа да дадем и допълнителна информация:


LonelyError = "Stefan is lonely!"
def make_lonely(level): raise MyError, level
try:
    make_lonely(666)
except LonelyError, level:
    print "Stefan's loneliness is at level %d" % level

Пораждане на изключителни ситуации — низове (2)

За да се определи дали едно изключение съвпада с конкретно except твърдение двете изключение се сравняват с is, а не с ==:

LonelyError0 = "Stefan is lonely!"
LonelyError1 = "Stefan is lonely!"

def make_lonely(): raise LonelyError0
try:
    make_lonely()
except LonelyError1:
    print LonelyError1

води до:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 1, in make_lonely
Stefan is lonely!

Пораждане на изключения — класове (1)

class StefanError:
    def __init__(self, msg = "There is something wrong with Stefan!"):
        self.msg = msg
    def __repr__(self): return self.msg
    def __str__(self): return self.__repr__()

class LonelyStefanError(StefanError):
    def __init__(self):
      StefanError.__init__(self, "Stefan is Lonely!")

class StupidStefanError(StefanError): pass

def make_lonely(): raise LonelyStefanError()
def eat_a_cockroach(): raise StupidStefanError

try:
    make_lonely()
except StupidStefanError:
	print "Stefan has been somehow stupid!"
except StefanError, data:
    print data

Пораждане на изключения — класове (2)

2 начина за пораждане:


За обратна съвместимост с низовите изключения могат да се използват и следните начини:

Пораждане на изключения — класове (3)

Пример:

class MyError():
    def __init__(self, msg, num):
        print "%s (%d)" % (msg, num)

try:
	raise MyError, ("Initialized", 99)
except MyError:
	pass
Initialized (99)

Далаверата от изключенията с класове

Същото, ама с низове

EmotionalStefanError = "Stefan has an emotional problem!"
LonelyStefanError = "Stefan is lonely!"
ConfusedStefanError = "Stefan is confused!"
MoneyStefanError = "Stefan is out of money!"

stefan = Stefan()
try:
    stefan.live_a_day()
except MoneyStefanError:
    stefan.rob_a_bank()
# налага се да опишем всички наследници на EmotionalError на ръка
# при всяко добавяне на грешка, трябва да я добавяме и тук
except (EmotionalStefanError, LonelyStefanError, ConfusedStefanError), problem:
    stefan.go_to_shrink(problem)
# тук пък трябва да опишем всички други грешки...
# което си е ад за поддръжка, почти като москвич осмак
except StefanError:
    stefan.call_911()
else:
    stefan.set_happy_bit()
finally:
    stefan.play_with(Girl(beauty=99, hair='blonde')*3)

Ескалиране на грешката

assert

Вградени класове за изключения

class Exception

Какво може да направи Exception за нас?

Използване на изключение не само за грешки

Аз хващам прекалено много

Ти пък прекалено малко

Нека обобщим

Няколко неща, за които може да ползваме изключения:

Още въпроси?