„ Програмиране с 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
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
Можем да прихванем и по-общо тип изключение (родителски клас):
def distribute_over2(beers):
try:
return 333/beers
except ArithmeticError:
return 0
Ето и доказателство:
>>> issubclass(ZeroDivisionError, ArithmeticError)
True
Тази практика е много логична, тъй като делението на нула е и аритметична грешка и когато прихващаме аритметичните грешки би трябвало да хванем и делението на нула.
По-гъвкаво прихващане
- Можем да вземем и повече информация за изключението:
Какво ще има вtry:
x = [] / 4
except TypeError, data:
print datadata
, зависи от самото изключение, но е прието всички да връщат годна за отпечатване стойност, ако се дадат като аргументи наstr
илиrepr
. - Ако за няколко изключения имаме една и съща реакция, можем да ги прихванем накуп:
try:
doomed()
except (NameError, TypeError), data:
print data
except (MyError, YourError):
print "Opps! This shouldn't've hapenned..."
except:
print "Unknown exception."
else:
print "It's my happy day!" - с празен
except
прихващаме изключения, които не са били хванати до момента. Трябва да бъде поставен след всички другиexcept
-и
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 "", line 2, in
File "", 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 начина за пораждане:
raise клас, инстанция # инстанцията е от точно този клас
raise инстанция # примерът със самотния Митьо, същото като
raise инстанция.__class__, инстанция
За обратна съвместимост с низовите изключения могат да се използват и следните начини:
raise клас # примерът с тъпия Митью,
същото като raise клас()raise клас, arg # същото като raise клас(arg)
raise клас, (arg0, arg1) # същото като raise клас(arg0, arg1)
Пример:
class MyError():
def __init__(self, msg, num):
print "%s (%d)" % (msg, num)
try:
raise MyError, ("Initialized", 99)
except MyError: pass
Initialized (99)
Далаверата от изключенията с класове
- Прихващат се всички наследници — така лесно можем да си структурираме типовете грешки.
class EmotionalMityoError(MityoError): pass
class LonelyMityoError(EmotionalMityoError): pass
class ConfusedMityoError(EmotionalMityoError): pass
class MoneyMityoError(MityoError): pass
mityo = Mityo()
try:
mityo.live_a_day()
except MoneyMityoError:
mityo.rob_a_bank()
# прихващаме по-общия проблем, а не по-частните Lonely и Confused
# от инстанцията на проблема психоаналитика може да извлече ценна информация
except EmotionalMityoError, 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)
Същото ама с низове
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)
Ескалиране на грешката
- Когато Python се натъкне на изключение в даден блок и в него то не се обработи, изключението се праща към горния блок, после към по-горния и така докато или изключението не бъде прехванато или не стигнем най-отгоре и интерпретаторът не спре програма по познатия ни вече начин (в червеничко).
- Можем да се намесим в следната схема или като прихванем изключението (вече знаем как), или като пратим изключението нагоре по трасето. Последното става с голо извикване на
raise
:try:
mityo.live_a_day()
except GirlfriendMityoError:
mityo.lonely = True
# Митьо не може да се оправя с това, нека тези отгоре да се грижат
raise
assert
assert <проверка>, [<данни>]
- Целта на твърдението
assert
е да се подсигурите, че важно за вашата програма условие е изпълнено assert test, data
е еквивалентно на:if __debug__:
if not test:
raise AssertionError, datadata
никак не е задължително- Както си личи от примера,
assert
рядко се ползва в крайния продукт, а най-вече по време на разработка за да си спестим главоболия и да сме сигурни в целостта на данните си - По подразбиране глобалният атрибут
__debug__
има стойност1
като може да бъде променена от вас или от опцията на командния ред-O
(оптимизация), която го установява наFalse
def fib(n):
assert n >= 0
if n <= 1:
return n
else:
return fib(n-2) + fib(n-1)
Вградени класове за изключения
- Основният, който всички наследяват е
Exception
- Главни категории:
StandardError
- родител на всички вградени изключения; директен наследник на
Exception
ArithmeticError
- родител на
OverflowError
,ZeroDivisionError
,FloatingPointError
LookupError
- родител на
IndexError
,KeyError
EnvironmentError
- родител на изключенията, които се случват извън интерпретора:
IOError
,OSError
- Какво може да направи
Exception
за нас?>>> class MyError(Exception): pass
>>> raise MyError("Failed, you have!", -1)
Traceback (most recent call last):
File "", line 1, in ?
__main__.MyError: ('Failed, you have!', -1)
>>> error = MyError("Failed, you have!", -1)
>>> error.args
('Failed, you have!', -1)- пази аргументите, които сме дали при създаване в
args
- дефинира
__str__
, така че да връща нещо като:map(str, self.args)
Използване на изключение не само за грешки
- за прихващане на събития през няколко блока дълбочина
try:
for box in boxes:
for jar in box.jars():
jar.has(throw=True, colour='velvet', eyes='cheese')
except JarFound, good_jar:
print "We found the jar! Its name is %s" % good_jar.name
else:
print "I couldn't find it :(" - за всякакви подобни странности на потока на програмата ни
- за обработка на частни случаи
Аз хващам прекалено много
def func():
try:
… # някъде тук възниква IndexError
except:
… # тук хващаме всичко и отпечатваме съобщение за грешка
try:
func()
except IndexError: # нъцки, няма да го хванем тук
…- прихващайте не повече отколкото ви трябва
- използвайте гол
except
главно в най-високото ниво на програмата си
Ти пък прекалено малко
try:
mityo.live_a_day()
except (MityoWantsACracker, MityoWantsADrink,
MityoWantsAnIsland, MityoWantsA333YearOldWhiskey,
MityoNeedsPanties, MityoNeedsNewSlippers), thing:
you.buy(whom=mityo, what=thing)- какво ще се случи ако Митьо може да поиска и мармотче? Ами само 111 годишно?
- винаги е по-добре изключенията да се структурират, за да се избегне дългото, кошмарно за поддръжка и податливо на много грешки изброяване:
except (MityoWants, MityoNeeds), thing:
…
Нека обобщим
Няколко неща, за които може да ползваме изключения:- обработка на грeшки:
- структурирани, прихващаеми, позволяващи предаване на допълнителна информация
- вградените функции и твърдения широко ги използват
- пораждане и прихващане на собствени изключения
- безусловно извършване на заключителни действия —
finally
- предаване на събития между отдалечени структурно части от кода
Няма коментари:
Публикуване на коментар