예외는 언제 어떻게 사용해야합니까?


20

설정

예외를 언제 어떻게 사용하는지 결정하는 데 종종 어려움이 있습니다. 간단한 예를 생각해 봅시다. " http://www.abevigoda.com/ "과 같이 웹 페이지를 긁어 Abe Vigoda가 아직 살아 있는지 확인 한다고 가정 해 봅시다 . 이렇게하려면 페이지를 다운로드하고 "Abe Vigoda"라는 문구가 나타나는 시간을 찾기 만하면됩니다. Abe의 상태가 포함되어 있으므로 첫 번째 모양을 반환합니다. 개념적으로 다음과 같습니다.

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

여기서 parse_abe_status(s)"Abe Vigoda is something " 형식의 문자열을 가져 와서 " something "부분을 반환합니다 .

Abe의 상태를 위해이 페이지를 긁어내는 훨씬 더 강력하고 강력한 방법이 있다고 주장하기 전에, 이것이 내가있는 일반적인 상황을 강조하기 위해 사용되는 단순하고 고안된 예일뿐임을 기억하십시오.

이 코드는 어디에 문제가 생길 수 있습니까? 다른 오류 중에서 "예상되는"오류는 다음과 같습니다.

  • download_page페이지를 다운로드하지 못할 수 있으며를 던집니다 IOError.
  • URL이 오른쪽 페이지를 가리 키지 않거나 페이지가 잘못 다운로드되어 적중이 없습니다. hits그런 다음 빈 목록입니다.
  • 웹 페이지가 변경되어 페이지에 대한 가정이 잘못되었을 수 있습니다. 어쩌면 Abe Vigoda에 대한 언급이 4 개일 것으로 예상되지만 5를 찾습니다.
  • 어떤 이유로 hits[0]"Abe Vigoda is something " 형식의 문자열이 아니므로 올바르게 구문 분석 할 수 없습니다.

첫 번째 경우는 실제로 문제가되지 않습니다. IOError가 발생하여 내 함수 호출자가 처리 할 수 ​​있습니다. 다른 경우와 처리 방법을 고려해 봅시다. 그러나 먼저, 우리가 parse_abe_status가능한 가장 어리석은 방법으로 구현한다고 가정 해 봅시다 .

def parse_abe_status(s):
    return s[13:]

즉, 오류 검사를 수행하지 않습니다. 이제 옵션으로 이동하십시오.

옵션 1 : 반품 None

발신자에게 다음을 반환하여 문제가 발생했다고 알릴 수 있습니다 None.

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    if not hits:
        return None

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

발신자가 수신하는 경우 None내 함수에서, 그는 더는 아베 비고 다의 언급하고, 그래서 거기 없다고 가정한다 뭔가 잘못. 그러나 이것은 매우 모호합니다. 그리고 hits[0]우리가 생각했던 것과 다른 경우에는 도움 이되지 않습니다.

반면에, 우리는 몇 가지 예외를 둘 수 있습니다 :

옵션 2 : 예외 사용

hits비어 있으면 IndexError시도 할 때가 발생 hits[0]합니다. 그러나 발신자는 그것이 IndexError어디에서 IndexError왔는지 전혀 모르기 때문에 내 함수에 의해 발생 되는 것을 처리해서는 안됩니다 . find_all_mentions그가 아는 ​​모든 것에 의해 던져 질 수 있었다 . 이를 처리하기 위해 사용자 정의 예외 클래스를 작성합니다.

class NotFoundError(Exception):
    """Throw this when something can't be found on a page."""

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    try:
        hits[0]
    except IndexError:
        raise NotFoundError("No mentions found.")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

이제 페이지가 변경되어 예상치 못한 히트가 발생하면 어떻게됩니까? 코드가 여전히 작동 할 수 있기 때문에 이것은 치명적이지 않지만 호출자는 매우 주의를 기울이거나 경고를 기록 할 수 있습니다. 그래서 나는 경고를 던질 것이다.

class NotFoundError(Exception):
    """Throw this when something can't be found on a page."""

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    try:
        hits[0]
    except IndexError:
        raise NotFoundError("No mentions found.")

    # say we expect four hits...
    if len(hits) != 4:
        raise Warning("An unexpected number of hits.")
        logger.warning("An unexpected number of hits.")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    # he's either alive or dead
    return status == "alive"

마지막으로, 우리는 그것이 status살아 있거나 죽지 않았다는 것을 알 수 있습니다 . 어쩌면, 이상한 이유로 오늘은로 밝혀졌습니다 comatose. 그런 다음 FalseAbe가 죽었다는 것을 암시 하기 때문에 돌아가고 싶지 않습니다 . 여기서 무엇을해야합니까? 아마도 예외를 던져라. 그러나 어떤 종류? 사용자 정의 예외 클래스를 작성해야합니까?

class NotFoundError(Exception):
    """Throw this when something can't be found on a page."""

def get_abe_status(url):
    # download the page
    page = download_page(url)

    # get all mentions of Abe Vigoda
    hits = page.find_all_mentions("Abe Vigoda")

    try:
        hits[0]
    except IndexError:
        raise NotFoundError("No mentions found.")

    # say we expect four hits...
    if len(hits) != 4:
        raise Warning("An unexpected number of hits.")
        logger.warning("An unexpected number of hits.")

    # parse the first hit for his status
    status = parse_abe_status(hits[0])

    if status not in ['alive', 'dead']:
        raise SomeTypeOfError("Status is an unexpected value.")

    # he's either alive or dead
    return status == "alive"

옵션 3 : 중간에

예외가있는 두 번째 방법이 바람직하다고 생각하지만 예외를 올바르게 사용하고 있는지 확실하지 않습니다. 더 숙련 된 프로그래머가이 문제를 어떻게 처리 할 수 ​​있는지 궁금합니다.

답변:


17

Python에서 권장하는 것은 예외를 사용하여 실패를 표시하는 것입니다. 정기적으로 실패를 예상하더라도 마찬가지입니다.

코드 호출자의 관점에서 살펴보십시오.

my_status = get_abe_status(my_url)

None을 반환하면 어떻게 되나요? 호출자가 get_abe_status가 실패한 경우를 구체적으로 처리하지 않으면 my_stats가 없음으로 계속 진행합니다. 나중에 버그를 진단하기 어려울 수 있습니다. 없음을 확인하더라도이 코드에는 get_abe_status ()가 실패한 이유가 없습니다.

그러나 예외를 제기하면 어떻게됩니까? 발신자가 사례를 구체적으로 처리하지 않으면 예외는 기본 예외 처리기에 도달하여 상향으로 전파됩니다. 그것은 당신이 원하는 것이 아니지만 프로그램의 다른 곳에 미묘한 버그를 도입하는 것이 좋습니다. 또한 예외는 첫 번째 버전에서 손실 된 문제에 대한 정보를 제공합니다.

호출자의 관점에서 반환 값보다 예외를 얻는 것이 더 편리합니다. 그리고 그것은 파이썬 스타일이며, 예외를 사용하여 값을 반환하지 않는 실패 조건을 나타냅니다.

일부는 다른 관점을 취하여 실제로 일어날 것으로 예상되지 않는 경우에만 예외를 사용해야한다고 주장합니다. 그들은 정상적으로 달리는 달리기가 예외를 일으키지 않아야한다고 주장한다. 이에 대한 한 가지 이유는 예외가 크게 비효율적이지만 실제로 파이썬에는 해당되지 않기 때문입니다.

코드에서 몇 가지 사항 :

try:
    hits[0]
except IndexError:
    raise NotFoundError("No mentions found.")

빈 목록을 확인하는 것은 정말 혼란스러운 방법입니다. 무언가를 확인하기 위해 예외를 유도하지 마십시오. if를 사용하십시오.

# say we expect four hits...
if len(hits) != 4:
    raise Warning("An unexpected number of hits.")
    logger.warning("An unexpected number of hits.")

logger.warning 라인이 절대 제대로 실행되지 않는다는 것을 알고 있습니까?


1
답변 해 주셔서 감사합니다. 게시 된 코드를 보면서 예외를 언제 어떻게 던지는 지에 대한 느낌이 향상되었습니다.
jme

4

수락 된 답변은 받아 들일 가치가 있으며 질문에 대답해야합니다. 저는 약간의 추가 배경을 제공하기 위해 작성합니다.

파이썬의 신조 중 하나는 허가보다 용서를 구하는 것이 더 쉽다는 것입니다. 즉, 일반적으로 작업 만 수행하고 예외가 예상되는 경우 예외를 처리합니다. 사전에 if를 확인하는 것과 달리 예외가 발생하지 않도록하십시오.

C ++ / Java와의 차이점이 얼마나 극적인지를 보여주는 예제를 제공하고 싶습니다. C ++의 for 루프는 일반적으로 다음과 같습니다.

for(int i = 0; i != myvector.size(); ++i) ...

myvector[k]k> = myvector.size ()에 액세스 하면 예외가 발생합니다. 따라서 원칙적으로 이것을 시도 어획량으로 작성할 수 있습니다.

    for(int i = 0; ; ++i)  {
        try {
           ...
        } catch (& std::out_of_range)
             break

또는 비슷한 것. 이제 파이썬 for 루프에서 일어나는 일을 고려하십시오.

for i in range(1):
    ...

이것은 어떻게 작동합니까? for 루프는 range (1)의 결과를 가져와 iter ()를 호출 하여 반복자를 가져옵니다.

b = range(1).__iter__()

그런 다음 각 루프 반복에서 다음으로 호출합니다.

>>> next(b)
0
>>> next(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

즉, 파이썬의 for 루프는 실제로 변장 시도를 제외하고는 시도입니다.

구체적인 질문이있는 한, 예외는 정상적인 기능 실행을 중지하고 별도로 처리해야 함을 기억하십시오. 파이썬에서는 함수의 나머지 코드를 실행할 시점이 없거나 함수에서 발생한 일을 정확하게 반영하는 리턴이 없을 때마다 자유롭게 던져야합니다. 함수에서 일찍 반환하는 것은 다릅니다. 일찍 반환하는 것은 이미 답변을 알아 냈으며 답변을 알아 내기 위해 나머지 코드가 필요하지 않음을 의미합니다. 대답을 알 수없는 경우 예외가 발생해야하며 응답을 결정하는 나머지 코드를 합리적으로 실행할 수 없습니다. 이제, 어떤 예외를 던질 것인지와 같이 "정확하게 반영"하는 것은 문서화의 문제입니다.

특정 코드의 경우 적중을 빈 목록으로 만드는 상황이 발생해야한다고 말하고 싶습니다. 왜? 글쎄, 당신의 기능이 설정되는 방식으로, 적중을 파싱하지 않고 답을 결정할 수있는 방법이 없습니다. 따라서 URL이 잘못되었거나 적중이 비어있어 적중을 구문 분석 할 수없는 경우 함수는 질문에 대답 할 수 없으며 실제로 시도조차 할 수 없습니다.

이 특별한 경우에, 나는 당신이 구문 분석하고 합리적인 대답을 얻지 못하더라도 (살아 있거나 죽었더라도) 여전히 던져야한다고 주장합니다. 왜? 함수가 부울을 리턴하기 때문입니다. None을 반환하면 고객에게 매우 위험합니다. 그들이 None에 if 체크를하면, 실패가 없을 것입니다. 그것은 단지 False로 취급 될 것입니다. 따라서 고객은 기본적으로 항상 if is None check를 수행해야합니다. 자동 실패를 원하지 않는 경우 ... 아마도 던져야합니다.


2

예외적 인 상황이 발생 하면 예외를 사용해야 합니다. 즉, 응용 프로그램을 올바르게 사용하면 발생해서는 안되는 것입니다. 분석법 소비자가 찾을 수없는 것을 검색하는 것이 허용되고 예상되는 경우 "찾을 수 없음"은 예외가 아닙니다. 이 경우 null 또는 "None"또는 {} 또는 빈 반환 집합을 나타내는 항목을 반환해야합니다.

반면에, 방법의 소비자가 항상 (어떻게 망쳐 놓지 않는 한) 검색 대상을 찾는 것을 기대한다면 예외가 아니라는 것을 발견하지 말아야합니다.

핵심은 예외 처리에 많은 비용이들 수 있다는 것입니다. 예외는 사람들이 발생한 이유를 해독 할 수 있도록 스택 추적과 같은 응용 프로그램의 상태에 대한 정보를 수집해야합니다. 나는 그것이 당신이하려는 일이라고 생각하지 않습니다.


1
값을 찾지 못하는 것이 허용된다고 결정한 경우 그 결과를 나타내는 데 사용하는 것에주의하십시오. 분석법에서 a를 반환하고 String지표로 "없음"을 선택한 경우 "없음"이 유효한 값이되지 않도록주의해야합니다. 또한 데이터를보고 값을 찾지 못하는 것과 데이터를 검색 할 수없는 것에는 차이가 있으므로 데이터를 찾을 수 없습니다. 이 두 경우에 대해 동일한 결과를 얻는다는 것은 가치가 없을 때 가시성이 없다는 것을 의미합니다.
unholysampler

인라인 코드 블록에는 백틱 (`)이 표시되어 있습니다. 아마도 "없음"과 관련이있을 것입니다.
Izkata

3
나는 이것이 파이썬에서 절대적으로 거짓이라는 것을 두려워합니다. C ++ / Java 스타일 추론을 다른 언어에 적용하고 있습니다. 파이썬은 예외를 사용하여 for 루프의 끝을 나타냅니다. 꽤나 예외적입니다.
Nir Friedman

2

내가 함수를 쓰고 있다면

 def abe_is_alive():

나는 그것을 쓸 것 return True또는 False내가 하나 또는 다른 절대적으로 확신 사례에서와 raise다른 경우 (예 : 오류 raise ValueError("Status neither 'dead' nor 'alive'")). 내 호출 함수가 부울을 기대하고 있기 때문에 확실하게 제공 할 수 없다면 정기적 인 프로그램 흐름이 계속되지 않아야합니다.

예상과 다른 수의 "적중"을 얻는 예와 같은 것들은 아마도 무시할 것입니다. 히트 중 하나가 여전히 내 패턴 "Abe Vigoda is {dead | alive}"와 일치하는 한 괜찮습니다. 이렇게하면 페이지를 다시 정렬 할 수 있지만 여전히 적절한 정보를 얻습니다.

오히려

try:
    hits[0] 
except IndexError:
    raise NotFoundError

명시 적으로 확인합니다.

if not hits:
    raise NotFoundError

이것이 "저렴한"경향이 있기 때문에 try.

나는 당신에 동의합니다 IOError; 나는 또한 웹 사이트 연결 오류 처리를 시도하지 않을 것입니다. 우리가 어떤 이유로 든 웹 사이트를 처리 할 수없는 경우 (우리가 질문에 대답하는 데 도움이되지 않기 때문에)이 웹 사이트를 처리 할 수있는 적절한 장소가 아니며 통과해야합니다 호출 기능으로.

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