이전 글 막바지에 터진 궁금증..
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
후아, 이런 저퀄리티의 글도 쓰는데 이리 오랜 시간이 걸리네요...
앞으로는 정말 짤막한 글로만 써야겠네요 하하핳!