[Python3] Exception! - 1

SangHun·2021년 5월 9일
0
post-thumbnail

왜 하죠?

학부생 시절 JAVA로 배울 때만 해도 이런 생각이 들었었다.

예외를 왜 던지고 잡고 하는거지...?

아니 그냥 에러 발생하면 바로 처리하면 되는거 아녀??

try-catch랑 raise는 왜 따로 만든거지?

그리고 throw랑 throws는 뭐가 다른데...

아직도 JAVA와는 서먹한 사이지만 파이썬과 좀 가까워진 뒤로 그 이유를 알게 되었다.
혹여나 JAVA 코드를 참고하시려는 분들께는 죄송하게 됐습니다..

아래 예시 코드를 보자.

# First example.

book_info = {
    "title": "Le Petit Prince",
    "author": "Saint-Exupéry",
    "illustrator": "Saint-Exupéry",
    "language": "French",
}

answer = {}

answer["title"] = book_info["title"]
answer["country"] = book_info["country"]
answer["genre"] = book_info["genre"]

print(answer)

위 코드는 당연히도 버그가 터집니다.
아래와 같은 출력이 나오죠.

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    answer["country"] = book_info["country"]
KeyError: 'country'

이걸 예외 처리 없이 해결하려면?
if 문을 써야겠죠!!

# First example.

book_info = {
    "title": "Le Petit Prince",
    "author": "Saint-Exupéry",
    "illustrator": "Saint-Exupéry",
    "language": "French",
}

answer = {}

if "title" in book_info:
    answer["title"] = book_info["title"]

if "country" in book_info:
    answer["country"] = book_info["country"]

if "genre" in book_info:
    answer["genre"] = book_info["genre"]

print(answer)
> {'title': 'Le Petit Prince'}

저 수많은 if 문...
별로 안 거슬리신다구요?
그럼 어쩔 수 없죠 뭐. 계속 위와 같은 방식으로 쓰셔도 될 겁니다.

그렇지만 쓸 수 밖에 없는 경우가 있죠.
파이썬에서 http 요청을 보낼 때 가장 많이 쓰이는
requests 모듈을 예로 들어봅시다!
가장 많이 라는 단어는 개인적인 의견입니다!

# Second example.

import requests

response = requests.get(
    url="http://some.com/api/user-data"
)

print(response.status_code)

자, 아무런 문제가 없어보이는 코드입니다.
정말로 그럴까요?

Traceback (most recent call last):
  File "my_request.py", line 5, in <module>
    requests = get(url="http://some.com/api/user-data")
    ...
    ...
requests.exceptions.Timeout: Oops, timeout exception!!

짜잔 !!
나는 아무런 문제 없이 코드를 작성했는데
뜬금없이 위와 같은 에러가 발생할 수 있습니다!

아잇, 뭐 짧게 짠 코드인데 실패하면 중단시키거나 내가 다시 실행시키면 되지

라는 마인드를 가질 수도 있지만,

코드가 몇 백줄이 넘어가고!
엄청나게 복잡한 시스템이고!
에러가 일일이 중단시키고 재실행하기 너무 힘들고!

뭐 이런 상황이 벌어질 수 있다는 거죠.
그리고 실제로도 벌어지고...

내가 짠 코드에서 직접 발생하는 에러가 아니라
시스템적인 문제라든지, 내가 사용하는 모듈에서 발생하는 문제는
최소한의 예외 처리가 없으면 대환장납니다!! 와우!!

적용해봅시다

1번 예시

위의 코드에 예외 처리를 적용해봅시다.

# First example.

book_info = {
    "title": "Le Petit Prince",
    "author": "Saint-Exupéry",
    "illustrator": "Saint-Exupéry",
    "language": "French",
}

answer = {}

try:
    answer["title"] = book_info["title"]
    answer["country"] = book_info["country"]
    answer["genre"] = book_info["genre"]

except KeyError as error:
    print(error)

print(answer)

이 코드는 {'title': 'Le Petit Prince'} 외에 하나의 출력이 더 나옵니다.
바로 'country' 인데요.
print(error) 라인에서 출력됩니다.

> 'country'
> {'title': 'Le Petit Prince'}

최종 출력은 위와 같습니다.

예외 처리되어 출력된 문구가 너무 밋밋하죠?
딱 봤을 때 왜 틀렸는지 알고 싶다!
그러면 아래처럼 꾸며줄 수 있습니다.

# First example.

book_info = {
    "title": "Le Petit Prince",
    "author": "Saint-Exupéry",
    "illustrator": "Saint-Exupéry",
    "language": "French",
}

answer = {}

try:
    answer["title"] = book_info["title"]
    answer["country"] = book_info["country"]
    answer["genre"] = book_info["genre"]

except KeyError as error:
    print(str(error) + " key is not in book_info!")

print(answer)

이렇게 하면 아래와 같이 나오죠.

> 'country' key is not in book_info!
> {'title': 'Le Petit Prince'}

아 이거도 너무 부족하다... 싶거나
저 에러가 나오게 된 경위를 모두 추적하고 싶다면?
즉, Stack trace를 출력하고 싶다면?

파이썬에서는 traceback이라는 라이브러리를 사용하면 됩니다.

# First example.

import traceback

book_info = {
    "title": "Le Petit Prince",
    "author": "Saint-Exupéry",
    "illustrator": "Saint-Exupéry",
    "language": "French",
}

answer = {}

try:
    answer["title"] = book_info["title"]
    answer["country"] = book_info["country"]
    answer["genre"] = book_info["genre"]

except KeyError as error:
    traceback.print_exc()

print(answer)

어떻게 출력되는지 볼까요?

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    answer["country"] = book_info["country"]
KeyError: 'country'
{'title': 'Le Petit Prince'}

처음 작성했던 코드와 다를게 없지 않냐?
에러가 그대로 뜨지 않냐?
라고 할 수 있지만 엄연히 다릅니다!

처음 작성했던 코드에서의 결과는

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    answer["country"] = book_info["country"]
KeyError: 'country'

입니다.
즉, 마지막 print(answer)문이 실행되지 않고 프로세스가 끝난거죠.

하지만 위 코드는 print(answer)문이 실행됩니다.
예외가 발생해도 우리가 처리를 해줬기 때문에 프로세스가 중단되지 않고 그대로 진행되는 겁니다!

2번 예시

뭐 간단하게 끝낼 수 있습니다!

# Second example.

import requests

try:
    response = requests.get(
        url="http://some.com/api/user-data"
    )
except requests.exception.Timeout as error:
    print(error)

print(response.status_code)

방금 우리가 마주쳤던 Timeout 에러는 처리될 겁니다.
단, print(response.status_code) 에서 200이 아니라 500같은 결과가 나오겠죠..
혹은 또다른 에러가 뜰 수도 있고요!

그런데 문득 궁금해집니다.

  1. Timeout이 아니라 다른 에러가 터지면 어쩌지?
  1. 에러가 발생하지 않았을 때에만 어떤 코드를 실행시키고 싶어!
  1. 에러가 발생하든 안하든 어떤 코드를 무조건 실행시키고 싶어!
  1. 예외 처리 코드를 더 깔끔하게 작성하고 싶어!

1번부터 차근히 보죠!
간단하게도, 내가 처리하고 싶은 예외를 추가해주면 됩니다.

# Second example.

import requests

try:
    response = requests.get(
        url="http://some.com/api/user-data"
    )
except requests.exception.Timeout as error:
    print(error)
except requests.exception.TooManyRedirects as error:
    print(error)
except requests.exceptions.RequestException as error:
    print(error)

print(response.status_code)

이렇게 여러 예외를 처리해야 할 경우에 주의해야 할 점!
상위 예외일 수록 처리하는 구문이 뒤에 가야 합니다!

위 코드로 설명드리자면,
requests.exception.Timeout
requests.exception.TooManyRedirects는 모두
requests.exceptions.RequestException를 상속받습니다.
그래서 RequestException 예외가 가장 뒤에 있죠.

예외 처리를 할 때 위와 같은 계층 구조를 확인하는 것을 추천드립니다!

그럼,
이 글을 쓴 진짜 이유를 얘기하면서
나머지 궁금증도 계속 해결하...
기에는 글이 너무 길어진 것 같군요!

다음 글에 계속~~ 하하하핳

2부에서 계속!

profile
개발괴발자

0개의 댓글