이전 글 막바지에 터진 궁금증..
1. Timeout이 아니라 다른 에러가 터지면 어쩌지?
- 에러가 발생하지 않았을 때에만 어떤 코드를 실행시키고 싶어!
- 에러가 발생하든 안하든 어떤 코드를 무조건 실행시키고 싶어!
- 예외 처리 코드를 더 깔끔하게 작성하고 싶어!
1번은 너굴맨이 처리했으니 2번, 3번을 차례대로 보도록 하자!
def find():
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"]
except KeyError as error:
print(str(error) + " key is not in book_info!")
else:
return answer
return 'something is happening...'
print(find())
else가 추가되었다. 이 코드의 결과는 어떻게 될까?
> 'country' key is not in book_info!
> something is happening...
try문 안에서 예외가 발생되어 except에서 예외가 처리되고 else는 실행되지 않고 return 'something is happening...'가 실행되었다.
위 코드에서 "country"를 "author"로 바꿔주면 아래와 같이 else문에 진입하는 결과를 볼 수 있다.
> {'title': 'Le Petit Prince', 'author': 'Saint-Exupéry'}
try문에 한 번 들어가기만 하면 무조건 실행되는 부분이다.
방금 본 else를 곁들인 코드를 보여드리겠습니다!
import datetime
def find():
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"]
except KeyError as error:
print(str(error) + " key is not in book_info!")
else:
return answer
finally:
print("find() is running...")
print("---")
print(datetime.datetime.now())
print("---")
return 'something is wrong!!'
print(find())
try문에 들어갔을 때, 무조건 현재 시간을 출력하고자 한다.
위 코드는 except에 진입하므로 결과는 아래와 같다.
> 'country' key is not in book_info!
> find() is running...
> ---
> 2021-05-15 17:42:13.969271
> ---
> something is wrong!!
이외에도 finally는 여러 용도로 쓰일 수 있다.
except나else에서return문이 있지만,return하기 전에 무조건 실행시키고 싶은 코드가 있거나
try문에서 잡아내지 못하는 예외가 발생할 때에도 실행시키고 싶은 코드가 있거나
except문 안에서 새로운 예외가 발생할 때도 실행시키고 싶은 코드가 있거나
등등...
추가로 반복문 안에서 사용되는 continue나 break문에 대해서도 무조건 실행된다.
def find():
info_key = ["title", "country", "language"]
book_info = {
"title": "Le Petit Prince",
"author": "Saint-Exupéry",
"illustrator": "Saint-Exupéry",
"language": "French",
}
for index, info in enumerate(info_key):
try:
print(book_info[info])
except KeyError as error:
print(f'{error} is not in book info!')
break
finally:
print(f'{index} key is {info}')
find()
위 코드에서 break에 걸려도 finally에 진입한다.
> Le Petit Prince
> 0 key is title
> 'country' is not in book info!
> 1 key is country
자, 대망의 더 알아보기 구간이다.
이 글을 쓰기 시작한 이유이기도 하다...
예외 처리 코드를 더 깔끔하게, 혹은 더 논리적으로 짜고 싶을 때 아래의 스킬들을 쓸 수 있다.
파이썬에는 기본적으로 만들어져 있는 built-in 예외가 있습니다.
여기있는 예외만으로 모든 예외 처리가 가능하긴 합니다.
그러나 공식 문서에도 User-defined exception이 괜히 있는게 아니죠!
아래 차례로 나오는 상황을 생각해봅시다
이전 글에서도 예시로 보여드렸던 requests가 있을 수 있죠.
requests의 exception은 모두 파이썬 기본 exception을 상속받아서 만들어졌습니다.
일종의 Custom Exception으로 볼 수 있죠.
공식 문서에서 설명하는 User-defined exception이 필요한 이유또한 사실 이거입니다.
사용자가 원하는 이름의 예외를 만들고, 사용자가 원하는대로 동작하는 예외를 만들 수 있어야 하죠.
방법은 간단합니다.
원하는 예외를 상속받은 class를 만들면 됩니다!
기본적으로는 Exception를 상속받아서 만들 수 있습니다.
class MyKeyError(Exception):
def __init__(self, key=None):
'''Raised when a key is not found'''
self.message = 'Wrong key!'
self.key = key
def __str__(self):
if self.key:
return f'{self.key} is not found!!'
else:
return f'The key is not found!!'
def find():
book_info = {
"title": "Le Petit Prince",
"author": "Saint-Exupéry",
"illustrator": "Saint-Exupéry",
"language": "French",
}
answer = {}
try:
try:
answer["title"] = book_info["title"]
answer["country"] = book_info["country"]
except KeyError as key_error:
raise MyKeyError(key_error)
except MyKeyError as error:
return error
return answer
print(find())
아주 간단하게 작성했습니다.
이 코드를 실행하면 아래와 같은 결과가 나옵니다.
> 'country' is not found!!
만약 raise MyKeyError(key_error)를 raise MyKeyError()로 바꾸면?
> The key is not found!!
참 쉽죠?
이건 뭘까요?
예외 체이닝?
파이썬 공식 문서들을 먼저 보자.
Errors and Exceptions - exception chaining
Built-in Exceptions
대강 설명드리자면,
여러 예외가 연결되어 발생할 때, 예외들을 연결하여
traceback로 보여줄 수 있게 합니다.
raise구문에서from을 써서 사용할 수 있습니다.
사실 이 기능은 except문이나 finally문에서 raise하면 자동으로 설정된다.
우리가 직접 from을 설정하여 출력 문구를 살짝 바꾸게 하거나,
우리가 from None으로 체이닝을 끊을 수 있다. (이런 경우는 상상하기 힘드네요...;;)
from을 직접 설정했을 경우 출력
Traceback (most recent call last):
File "/Users/sh/test_codes/ts.py", line 53, in <module>
func()
File "/Users/sh/test_codes/ts.py", line 50, in func
raise IOError
OSError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/sh/test_codes/ts.py", line 55, in <module>
raise RuntimeError('Failed!') from exc
RuntimeError: Failed!
from을 직접 설정하지 않은 경우 출력
Traceback (most recent call last):
File "/Users/sh/test_codes/ts.py", line 53, in <module>
func()
File "/Users/sh/test_codes/ts.py", line 50, in func
raise IOError
OSError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/sh/test_codes/ts.py", line 55, in <module>
raise RuntimeError('Failed!')
RuntimeError: Failed!
차이가 보이시나요?
두 예외 사이의 문구 한 줄만 다를 뿐입니다.
여기서 한 가지 예시를 더 보죠!
from None으로 설정한 경우 출력
Traceback (most recent call last):
File "/Users/sh/test_codes/ts.py", line 55, in <module>
raise RuntimeError('Failed!') from None
RuntimeError: Failed!
위 예시들과 달리, IOError는 표시되지 않고 RuntimeError만 표시됩니다.
좀 급작스럽게 마무리되는 듯 하네요.
User-defined Exception에 대해서는 더 공부해볼 필요를 느꼈습니다.
Exception Chaining은 글을 쓰면서 공부하다보니 제가 잘못 알고 있던 부분도 알게 되었습니다.. 허허
그리고 traceback 또한 더 공부를 해야겠습니다.
예시 코드를 작성하다가 파이썬 예외에 있는 tb_frame 어트리뷰트를 알게 되었는데,
몇 예외들은 이 어트리뷰트를 가지고 있지 않더군요.
예) KeyError
후아, 이런 저퀄리티의 글도 쓰는데 이리 오랜 시간이 걸리네요...
앞으로는 정말 짤막한 글로만 써야겠네요 하하핳!