Изключения

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

Стефан Кънев & Николай Бачийски

21.03.2007г.

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

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

"""Модул за зимнината на Митьо Питона"""
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:
        raise MityoWinterError

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

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 badfunc
    def badfunc(): 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 = "Mityo is lonely!"
def make_lonely(): raise LonelyError
try:
    make_lonely()
except LonelyError:
    print LonelyError

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

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

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

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

LonelyError0 = "Mityo is lonely!"
LonelyError1 = "Mityo 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
Mityo is lonely!

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

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

class LonelyMityoError(MityoError):
    def __init__(self):
      MityoError.__init__(self, "Mityo is Lonely!")

class StupidMityoError(MityoError): pass

def make_lonely(): raise LonelyMityoError()

def eat_a_cockroach(): raise StupidMityoError

try:
    make_lonely()
except StupidMityoError:
	print "Mityo has been somehow stupid!"
except MityoError, data:
    print data

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

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


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

Пример:

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

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

Initialized (99)

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

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

EmotionalMityoError = "Mityo has an emotional problem!"
LonelyMityoError = "Mityo is lonely!"
ConfusedMityoError = "Mityo is confused!"
MoneyMityoError = "Mityo is out of money!"

mityo = Mityo()
try:
    mityo.live_a_day()
except MoneyMityoError:
    mityo.rob_a_bank()

# налага се да опишем всички наследници на EmotionalError на ръка
# при всяко добавяне на грешка, трябва да я добавяме и тук
except (EmotionalMityoError, LonelyMityoError, ConfusedMityoError), problem:
    mityo.go_to_shrink(problem)

# тук пък трябва да опишем всички други грешки...
# което си е ад за поддръжка, почти като москвич осмак
except MityoError:
    mityo.call_911()
else:
    mityo.set_happy_bit()
finally:
    mityo.play_with(Girl(beauty=99, hair='blonde')*3)

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

assert

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

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

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

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

Нека обобщим

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

Още въпроси?