06. Изключения и with
Преди това
Ако все още се чудите защо да не използвате Python 2
Традицията повелява
"""Модул за зимнината на Митьо Питона"""
import jars
ERROR = -1
SUCCESS = 0
def prepare_for_winter()
jar = jars.Jar()
if jar.clean() == jars.ERROR
print("Shit happens")
return ERROR
if jar.fill('python juice') == jars.ERROR
print("Shit happens")
return ERROR
if jar.close() == jars.ERROR
print("Shit happens")
return ERROR
return SUCCESS
Традициите не са това…
"""Модул за зимнината на Митьо Питона"""
import jars
class MityoWinterError(Exception): pass
def prepare_for_winter()
try:
jar = jars.Jar()
jar.clean()
jar.fill('python juice')
jar.close()
except jars.Error
print("Shit happens")
Синтаксис и семантика
try
блок
except изключения
блок ако се случи някое от описаните изключения
…
except още изключения
блок ако се случи някое от описаните изключения
except
блок ако изключението не е хванато по-горе
else
блок ако не е възникнала изключителна ситуация
finally
блок изпълнява се винаги
Вградените изключения
Основният, който всички наследяват е BaseException, но най-съществените наследяват от Exception
- StandardError родител на всички вградени изключения; директен наследник на Exception
- ArithmeticError родител на OverflowError, ZeroDivisionError, FloatingPointError
- LookupError родител на IndexError, KeyError
- EnvironmentError родител на изключенията, които се случват извън интерпретора: IOError, OSError
Повече информация рядко е излишна
try
x = [] / 4
except TypeError as data
print(data)
Какво ще има в data, зависи от самото изключение, но е прието всички да връщат годна за отпечатване стойност, ако се дадат като аргументи на str или repr.
Ако за няколко изключения имаме една и съща реакция, можем да ги прихванем накуп
try
doomed()
except (NameError, TypeError) as 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 as data
print("Couldn't read from file:", data)
except (mymodule.BadDataError, mymodule.InternalError) as data
print('Loading failed:', data)
else
print('Data loaded successfully from file.')
finally
file.close()
Ако присъства, finally стои винаги най-отдолу.
Създаване на изключения
class XmasError(Exception)
def __init__(self):
self.issuer, self.message = 'Robosanta', 'watches you'
class NaughtyError(XmasError)
def __init__(self):
super().__init__()
self.message = 'You were very naughty this year!'
class AreYouDeadYetError(XmasError)
def __init__(self):
super().__init__()
self.message = 'Are you dead yet?'
def confess_sins(): raise NaughtyError
def celebrate_xmas(): raise AreYouDeadYetError
Ескалиране на грешката
- Когато Python се натъкне на изключение в даден блок и в него то не се обработи, изключението се праща към горния блок, после към по-горния и така докато или изключението не бъде прехванато или не стигнем най-отгоре и интерпретаторът не спре програма по познатия ни вече начин (в червеничко).
- Можем да се намесим в следната схема или като прихванем изключението (вече знаем как), или като пратим изключението нагоре по трасето. Последното става с голо извикване на raise
try
bender.live_a_day()
except BenderError
bender.boned = True
# Бендър не може да се оправя с това, нека тези отгоре да се грижат
raise
Подходи
- Look Before You Leap (LBYL)
- Easier to Ask for Forgiveness than Permission (EAFP)
Нека обобщим
Няколко неща, за които може да ползваме изключения
обработка на грeшки:
- структурирани, прихващаеми, позволяващи предаване на допълнителна информация
- вградените функции и твърдения широко ги използват
- пораждане и прихващане на собствени изключения
безусловно извършване на заключителни действия — finally
Finally finally?
- Искаме да обърнем реда на редовете на файл?
try
source_file = open(src, 'r')
buffer = []
try
buffer = source_file.readlines()
finally
source_file.close()
target_file = open(target, 'w')
try
for line in reversed(buffer):
target_file.write(line)
finally
target_file.close()
except IOError
print("Tough luck, junior")
Too long; didn't read?
buffer = []
try
with open(src) as source_file:
buffer = source_file.readlines()
with open(target) as target_file
for line in reversed(buffer):
target_file.write(line)
except IOError
print("Much better, now, ain't it?")
- with гарантира, че файлът ще бъде затворен автоматично.
with
with израз [as име]
блок
- Резултатът от израза се нарича Context Manager
- Изпълнява се метода __enter__() на CM и резултатът се записва в името след as
- Изпълнява се блока
- Ако е настъпило излючение се изпълнява __exit__(type, value, traceback) на CM
- Ако не е настъпило излючение се изпълнява __exit__(None, None, None) на CM
with нагледно
with open('/etc/passwd') as source_file
buffer = source_file.readlines()
print('Done!')
е същото като
source_file = open('/etc/passwd').__enter__()
try
buffer = source_file.readlines()
source_file.__exit__(None, None, None)
except Exception
source_file.__exit__(*sys.exc_info())
print('Done!')
Малък пример
class Manager
def __enter__(self):
print("I've been entered!")
return 42
def __exit__(self, type, value, traceback)
print("I've been exited!")
with Manager() as something
print("Am I inside?")
print(something)
# I've been entered!
# Am I inside?
# 42
# I've been exited!
with с няколко аргумента
with foo() as f, bar() as b
...
е същото като
with foo() as f
with bar() as b:
...
contextlib
Вграденият модул contextlib ни предлага три много полезни Context Manager-а
- closing
- contextmanager
- ContextDecorator
closing
contextlib.closing вика метода close на обекта, с който работим, след изпълнение на блока
class closing(object)
def __init__(self, thing): self.thing = thing
def __enter__(self): return thing
def __exit__(self, type, value, traceback)
self.thing.close()
...и ви позволява да пишете следното
from contextlib import closing
import codecs
with closing(urllib.urlopen('http://www.python.org')) as page
for line in page:
print(line)
Още въпроси?
- Пишете ни на fmi@py-bg.net
- Страница на курса: http://fmi.py-bg.net/
- Форуми на курса: http://fmi.py-bg.net/topics
- Курсът в Twitter: http://twitter.com/pyfmi
- Курсът във Facebook: http://www.facebook.com/group.php?gid=104970619536589