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

Преди това

Ако все още се чудите защо да не използвате Python 2

PEP 404 - Python 2.8 Un-release Schedule

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

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

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

        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
    

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

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

Подходи

Нека обобщим

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

обработка на гр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 израз [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)
    

Още въпроси?