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


293

부끄러운 일로, 파이썬 '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'문에서 실패를 처리하기 위해 무엇을 할 수 있습니까?


당신은 무엇을 의미합니까 "예외가 발생하지 않습니다 다른 작동하지 않는 문을 제외 / 시도에 '와'둘러싸는" ? with문은 마술 주변 중단하지 않는 try...except문.
Aran-Fey

4
흥미롭게도 Java의 try-with-resources 문 원하는이 사용 사례를 정확하게 지원합니다. docs.oracle.com/javase/tutorial/essential/exceptions/…
Nayuki

답변:


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'

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

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 / except 블록 안에 있습니다 (내부 try / expect 블록이 있더라도 탈출 할 수있는 예외는 여전히 바깥 쪽을 치십시오). Douglas는 그 차이가 중요한 경우를 해결하기 위해 두 번째 예를 추가했습니다.
ncoghlan 2016 년

3
이 예제에서 파일이 닫히나요? "with"범위 밖에서 열렸 기 때문에 묻습니다.
Mike Collins

6
@MikeCollins 'with'를 종료하면 파일이 'with'보다 먼저 열려 있어도 열린 파일이 닫힙니다.
user7938784

75

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

38
나는 그것을 좋아하지만 너무 많은 흑 마법 같은 느낌이 든다. 독자들에게 완전히 명백한 것은 아닙니다
Paul Seeb

5
@PaulSeeb 왜 그것을 정의하고 필요할 때마다하지 않아도 되는가? 응용 프로그램 수준에서 정의되며 다른 컨텍스트 관리자와 마찬가지로 마술입니다. with 문을 사용하는 누군가가 그것을 명확하게 이해할 것이라고 생각합니다 (기능 이름이 마음에 들지 않으면 더 표현력이 좋을 수도 있습니다). "with"문 자체는 이러한 방식으로 작동하여 "보안"코드 블록을 정의하고 검사 기능을 컨텍스트 관리자에게 위임하여 코드를 더 명확하게합니다.

9
이 모든 문제는 단지 사용자 코드에 finally 블록을 쓰지 않기 때문입니다. 나는 우리 모두 with 문에 대한 과대 광고 증상으로 고통 받고 있다고 생각하기 시작했습니다.
jgomo3

1
파이썬에서 예외를 처리하는 가장 좋은 방법은 예외를 포착하고 반환하는 함수를 작성하는 것입니까? 진심이야? 예외를 처리하는 pythonic 방법은 try...except명령문 을 사용하는 것입니다.
Aran-Fey

58

파이썬 '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

2

복합 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__
    ...
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.