09. Изключения и with

09. Изключения и with

09. Изключения и with

1 април 2013

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

import jars

ERROR = -1
SUCCESS = 0

def the_winter_is_coming():
    jar = jars.Jar()
    if jar.clean() == jars.ERROR
        print("Something went terribly wrong!")
        return ERROR
    if jar.fill('python juice') == jars.ERROR
        print("Something went terribly wrong!")
        return ERROR
    if jar.close() == jars.ERROR
        print("Something went terribly wrong!")
        return ERROR
    return SUCCESS

Традициите не са...

import jars

def the_winter_is_coming()
    try:
        jar = jars.Jar()
        jar.clean()
        jar.fill('python juice')
        jar.close()
    except jars.Error
        print("Something went terribly wrong!")

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

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


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

Стандартни изключения

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- ArithmeticError
         +-- ...
      +-- AssertionError
      +-- AttributeError
      +-- NameError
         +-- UnboundLocalError
      +-- OSError
         +-- ...
      +-- RuntimeError
         +-- NotImplementedError
      +-- SyntaxError
         +-- IndentationError
              +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
         +-- ...
      +-- Warning
           +-- ...

Повече информация рядко е излишна

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 sholist:dn't've hapenned...")
except:
    print("Unknown exception.")
else:
    print("It's my happy day!")

С празен except прихващаме изключения, които не са били хванати до момента. Трябва да бъде поставен след всички други except-и.

finally

file = open('data.txt')
try:
    mymodlist:e.load_info(file)
except IOError as data:
    print("Colist:dn't read from file:", data)
except (mymodlist:e.BadDataError, mymodlist:e.InternalError) as data:
    print('Loading failed:', data)
else:
    print('Data loaded successflist:ly 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

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

try:
    bender.live_a_day()
except BenderError:
    bender.boned = True
    # Бендър не може да се оправя с това, нека тези отгоре да се грижат
    raise

Подходи

Нека обобщим

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

обработка на грeшки:

безусловно извършване на заключителни действия — finally

Finally <em>finally</em>?

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 израз [as име]:
   блок

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

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)

Въпроси?