[python] 파이썬 ‘with’문을 사용하는 동안 예외 잡기

부끄러운 일로, 파이썬 ‘with’문에 대한 예외를 처리하는 방법을 알 수 없습니다. 코드가있는 경우 :

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

나는 somehing을하기 위해 ‘파일을 찾을 수 없음 예외’를 처리하고 싶습니다. 그러나 나는 쓸 수 없다

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

쓸 수 없다

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

try / except 문에서 ‘with’를 묶으면 작동하지 않습니다. 예외가 발생하지 않습니다. 파이썬으로 ‘with’문에서 실패를 처리하기 위해 무엇을 할 수 있습니까?



답변

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'

개방형 호출과 작업 코드의 오류를 다르게 처리하려면 다음을 수행하십시오.

try:
    f = open('foo.txt')
except IOError:
    print('error')
else:
    with f:
        print f.readlines()


답변

with진술을 활용하여이를 수행하는 가장 좋은 “Pythonic”방법 은 PEP 343의 Example # 6에 나열되어 있으며 , 진술의 배경을 제공합니다.

@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")


답변

파이썬 ‘with’문을 사용하는 동안 예외 잡기

with 문은 Python 2.6 이후__future__ 가져 오기 없이 사용할 수 있습니다 . 다음과 같이 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하고이 오류에 대해서는 catch 일 수 IOError있습니다. 처리 할 준비가 된 오류 만 포착하십시오.

따라서이 경우 다음을 수행하십시오.

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


답변

복합 with문장 에서 발생하는 예외의 가능한 원인을 구별

with명령문 에서 발생하는 예외를 구별하는 것은 다른 위치에서 발생할 수 있으므로 까다 롭습니다. 다음 장소 중 하나 (또는 ​​여기에서 호출되는 함수)에서 예외가 발생할 수 있습니다.

  • ContextManager.__init__
  • ContextManager.__enter__
  • with
  • ContextManager.__exit__

자세한 내용은 컨텍스트 관리자 유형 에 대한 설명서를 참조하십시오 .

이러한 서로 다른 경우를 구별하려면 with을 감싸는 것만 으로 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__가장 바깥쪽에 true 값을 반환하여 예외를 억제하는 경우)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”문 은 동등한 “non-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)

일반적으로 간단한 접근 방식은 잘 작동합니다.

이러한 특별한 예외 처리의 필요성은 매우 드물며 일반적으로 전체 withtry ... 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__
    ...


답변