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