Поймать исключение при использовании Python с оператором

293

К моему стыду, я не могу понять, как обработать исключение для оператора python «with». Если у меня есть код:

with open("a.txt") as f:
    print f.readlines()

Я действительно хочу обработать «файл не найден исключение», чтобы сделать что-то. Но я не могу написать

with open("a.txt") as f:
    print f.readlines()
except:
    print 'oops'

и не могу написать

with open("a.txt") as f:
    print f.readlines()
else:
    print 'oops'

Заключение 'with' в оператор try / исключением больше не работает: исключение не вызывается. Что я могу сделать, чтобы обработать сбой внутри оператора with с помощью Pythonic?

grigoryvp
источник
Что вы имеете в виду, что «вложение» с помощью оператора try / исключением больше не работает: исключение не вызывается » ? withЗаявление не волшебно разорвать ограждающую try...exceptзаявление.
Аран-Фей
4
Интересно, примерочных с-ресурсов заявление в Java делает поддержку именно этот случай использования вы хотите. docs.oracle.com/javase/tutorial/essential/exceptions/…
Наюки

Ответы:

256
from __future__ import with_statement

try:
    with open( "a.txt" ) as f :
        print f.readlines()
except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
    print 'oops'

Если вы хотите по-разному обрабатывать ошибки от вызова open и рабочего кода, который вы можете сделать:

try:
    f = open('foo.txt')
except IOError:
    print('error')
else:
    with f:
        print f.readlines()
Дуглас Лидер
источник
3
Как отмечается в stackoverflow.com/questions/5205811/… , блок try здесь действительно слишком широк. При создании диспетчера контекста и в теле оператора with не делается различий между исключениями, поэтому оно может быть не действительным решением для всех случаев использования.
ncoghlan
@ncoghlan Но вы можете добавить дополнительные try...exceptблоки внутрь, withчтобы быть ближе к источнику исключения, которое не имеет ничего общего с open().
rbaleksandar
1
@rbaleksandar Если я правильно помню, мой комментарий строго относился к первому примеру в ответе, где весь оператор with находится внутри блока try / исключением (так что даже если у вас есть внутренние блоки try / receive, любые исключения, которые они пропускают, будут все еще ударил внешнюю). Впоследствии Дуглас добавил второй пример для рассмотрения случаев, когда это различие имеет значение.
ncoghlan
3
Будет ли файл закрыт в этом примере? Я спрашиваю, потому что он был открыт за пределами области «с».
Майк Коллинз
6
@MikeCollins Выход из «с» закроет открытый файл, даже если файл открыт перед «с».
user7938784
75

Лучший «Pythonic» способ сделать это, используя withоператор, приведен в Примере № 6 в PEP 343 , который дает основу для оператора.

@contextmanager
def opened_w_error(filename, mode="r"):
    try:
        f = open(filename, mode)
    except IOError, err:
        yield None, err
    else:
        try:
            yield f, None
        finally:
            f.close()

Используется следующим образом:

with opened_w_error("/etc/passwd", "a") as (f, err):
    if err:
        print "IOError:", err
    else:
        f.write("guido::0:0::/:/bin/sh\n")
ОНК
источник
38
Мне это нравится, но кажется, что слишком много черной магии. Это не совсем ясно для читателя
Пол Сиб
5
@PaulSeeb Почему бы вам не определить это и не избавиться от этого каждый раз, когда вам это нужно? Он определен на уровне вашего приложения и так же волшебен, как и любой другой менеджер контекста. Я думаю, что кто-то, использующий оператор with, поймет это ясно (имя функции также может быть более выразительным, если вам это не нравится). Сам оператор «with» был разработан для работы таким образом, чтобы определить «безопасный» блок кода и делегировать функции проверки менеджерам контекста (чтобы сделать код более понятным).
9
Все эти проблемы только за то, что не записали блок finally в пользовательском коде. Я начинаю думать, что мы все страдаем от долгого шумихи над утверждением «с».
jgomo3
1
Лучший способ обработки исключений в Python - написать функцию, которая перехватывает и возвращает их? Шутки в сторону? Питонический способ обработки исключений - использовать try...exceptоператор.
Аран-Фей
58

Поймать исключение при использовании Python с оператором

Оператор with был доступен без __future__импорта с Python 2.6 . Вы можете получить его уже в Python 2.5 (но сейчас самое время обновить его) с помощью:

from __future__ import with_statement

Вот самая близкая вещь, которую нужно исправить. Вы почти у цели, но у withвас нет exceptпункта:

with open("a.txt") as f: 
    print(f.readlines())
except:                    # <- with doesn't have an except clause.
    print('oops')

__exit__Метод менеджера контекста , если он вернется, Falseвызовет ошибку, когда она завершится. Если он вернется True, он его подавит. openВстроенное это __exit__не возвращается True, так что вам просто нужно гнездо его в попытке, за исключением блока:

try:
    with open("a.txt") as f:
        print(f.readlines())
except Exception as error: 
    print('oops')

И стандартный шаблон: не используйте голый, except:который ловит BaseExceptionи каждое возможное исключение и предупреждение. Будьте как минимум так же конкретны, как Exceptionи для этой ошибки, возможно, поймайте IOError. Только ловить ошибки, которые вы готовы обрабатывать.

Так что в этом случае вы бы сделали:

>>> try:
...     with open("a.txt") as f:
...         print(f.readlines())
... except IOError as error: 
...     print('oops')
... 
oops
Аарон Холл
источник
2

Различение возможных источников исключений, возникающих из составного withоператора

Различать исключения, возникающие в withвыражении, довольно сложно, поскольку они могут возникать в разных местах. Исключения могут быть вызваны из любого из следующих мест (или вызываемых там функций):

  • ContextManager.__init__
  • ContextManager.__enter__
  • тело with
  • ContextManager.__exit__

Для получения более подробной информации см. Документацию о типах Context Manager .

Если мы хотим провести различие между этими различными случаями, просто завернуть withв a try .. exceptнедостаточно. Рассмотрим следующий пример (используя ValueErrorв качестве примера, но, конечно, его можно заменить любым другим типом исключения):

try:
    with ContextManager():
        BLOCK
except ValueError as err:
    print(err)

Здесь exceptбудут вылавливаться исключения, возникающие во всех четырех разных местах и, следовательно, не позволяющие различать их. Если мы переместим создание экземпляра объекта менеджера контекста за пределы with, мы можем различить __init__и BLOCK / __enter__ / __exit__:

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        with mgr:
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        # At this point we still cannot distinguish between exceptions raised from
        # __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
        pass

Фактически это только помогло с __init__частью, но мы можем добавить дополнительную переменную часового, чтобы проверить, является ли тело withзапущенного для выполнения (то есть, различие между __enter__и другими):

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        entered_body = False
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        else:
            # At this point we know the exception came either from BLOCK or from __exit__
            pass

Сложность состоит в том, чтобы различать исключения, происходящие из BLOCKи __exit__потому, что withбудет передано исключение, выходящее за пределы тела, __exit__которое может решить, как его обработать (см. Документы ). Однако, если он __exit__поднимется сам, исходное исключение будет заменено новым. Чтобы справиться с этими случаями, мы можем добавить общее exceptпредложение в теле withдля хранения любого потенциального исключения, которое в противном случае могло бы остаться незамеченным, и сравнить его с тем, которое попало в крайний случай exceptпозже - если они совпадают, это означает, что источник был BLOCKили иначе это было __exit__(в случае __exit__подавления исключения, возвращая истинное значение самое внешнееexcept просто не будет казнен).

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    entered_body = exc_escaped_from_body = False
    try:
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
            except Exception as err:  # this exception would normally escape without notice
                # we store this exception to check in the outer `except` clause
                # whether it is the same (otherwise it comes from __exit__)
                exc_escaped_from_body = err
                raise  # re-raise since we didn't intend to handle it, just needed to store it
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        elif err is exc_escaped_from_body:
            print('BLOCK raised:', err)
        else:
            print('__exit__ raised:', err)

Альтернативный подход с использованием эквивалентной формы, упомянутой в PEP 343

PEP 343. - Оператор «with» определяет эквивалентную «не-» версию withоператора. Здесь мы можем легко обернуть различные части try ... exceptи, таким образом, провести различие между различными потенциальными источниками ошибок:

import sys

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        value = type(mgr).__enter__(mgr)
    except ValueError as err:
        print('__enter__ raised:', err)
    else:
        exit = type(mgr).__exit__
        exc = True
        try:
            try:
                BLOCK
            except TypeError:
                pass
            except:
                exc = False
                try:
                    exit_val = exit(mgr, *sys.exc_info())
                except ValueError as err:
                    print('__exit__ raised:', err)
                else:
                    if not exit_val:
                        raise
        except ValueError as err:
            print('BLOCK raised:', err)
        finally:
            if exc:
                try:
                    exit(mgr, None, None, None)
                except ValueError as err:
                    print('__exit__ raised:', err)

Обычно более простой подход подойдет

Необходимость такой специальной обработки исключений должна быть довольно редкой, и обычно достаточно будет обернуть все withв try ... exceptблок. Особенно, если различные источники ошибок указаны разными (пользовательскими) типами исключений (менеджеры контекста должны быть разработаны соответствующим образом), мы можем легко различить их. Например:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...
Гость
источник