[Python3] Exception! - 2

SangHun·2021년 5월 15일
0
post-thumbnail
post-custom-banner

이전 글 막바지에 터진 궁금증..

1. Timeout이 아니라 다른 에러가 터지면 어쩌지?

  1. 에러가 발생하지 않았을 때에만 어떤 코드를 실행시키고 싶어!
  1. 에러가 발생하든 안하든 어떤 코드를 무조건 실행시키고 싶어!
  1. 예외 처리 코드를 더 깔끔하게 작성하고 싶어!

1번은 너굴맨이 처리했으니 2번, 3번을 차례대로 보도록 하자!

try, except, else, finally

else - 성공했을 때만

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'}

finally - 무조건

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는 여러 용도로 쓰일 수 있다.

  1. exceptelse에서 return문이 있지만, return하기 전에 무조건 실행시키고 싶은 코드가 있거나
  1. try문에서 잡아내지 못하는 예외가 발생할 때에도 실행시키고 싶은 코드가 있거나
  1. except문 안에서 새로운 예외가 발생할 때도 실행시키고 싶은 코드가 있거나

등등...
추가로 반복문 안에서 사용되는 continuebreak문에 대해서도 무조건 실행된다.

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

더 알아보기 (궁서체)

자, 대망의 더 알아보기 구간이다.
이 글을 쓰기 시작한 이유이기도 하다...
예외 처리 코드를 더 깔끔하게, 혹은 더 논리적으로 짜고 싶을 때 아래의 스킬들을 쓸 수 있다.

Custom Exception

파이썬에는 기본적으로 만들어져 있는 built-in 예외가 있습니다.
여기있는 예외만으로 모든 예외 처리가 가능하긴 합니다.

그러나 공식 문서에도 User-defined exception이 괜히 있는게 아니죠!

아래 차례로 나오는 상황을 생각해봅시다

다양한 프레임워크나 라이브러리를 사용

이전 글에서도 예시로 보여드렸던 requests가 있을 수 있죠.
requestsexception은 모두 파이썬 기본 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!!

참 쉽죠?

Exception Chaining

이건 뭘까요?
예외 체이닝?
파이썬 공식 문서들을 먼저 보자.
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

후아, 이런 저퀄리티의 글도 쓰는데 이리 오랜 시간이 걸리네요...
앞으로는 정말 짤막한 글로만 써야겠네요 하하핳!

profile
개발괴발자
post-custom-banner

0개의 댓글