본문 바로가기

Computer/Python

예외 처리에서의 동작 수행 - try/except/else/finally

반응형

파이썬에서 예외를 처리하는 과정에서 특정 동작을 수행하고 싶을 때 try, except, else finally 구문을 사용합니다. 전체 복합문에서 각 블록은 서로 다른 목적에 쓰이며, 다양하게 조합하면 유용합니다.

finally

예외가 발생하면 본래 호출 스택의 위 (함수 자신을 호출한 함수 쪽)로 예외를 전달해야 하지만, 예외가 발생하더라도 정리 코드를 실행해야 한다면 try/finally 블록을 사용합니다. 특히, 파일을 열고 안전하게 닫는 경우에 자주 사용합니다.

def try_finally_example(filename):
    print('파일 열기')
    handle = open(filename, encoding='utf-8')
    try:
        print('데이터 읽기')
        return handle.read()
    finally:
        print('close() 호출')
        handle.close()

if __name__ == '__main__':
    filename = 'random_data.txt'

    with open(filename, 'wb') as f:
        f.write(b'\xf1\xf2\xf3\xf4\xf5') # 잘못된 utf-8
    data = try_finally_example(filename)

위의 코드에서 read 메서드에 예외가 발생하면 항상 try_finally_example 함수를 호출한 쪽으로 예외가 전달되지만 finally 블록에 있는 handle의 close 메소드가 먼저 호출됩니다.

만약 try 블록 내에서 예외가 발생하지 않고 open 메소드에서 예외가 발생할 경우 finally 블록을 거치지 않고 바로 호출한 함수로 예외가 전달됩니다.

if __name__ == '__main__':
    data = try_finally_example('does_not_exist.txt')

 

else

코드에서 처리할 예외와 호출 스택을 거슬러 올라가며 전달할 예외를 명확히 구분하기 위해서는 try/else/finally 블록을 사용합니다. try 블록이 예외를 발생시키지 않으면 else 블록이 실행되는데 이를 통해 try 블록 안에 들어갈 코드를 최소화하여 발생할 여지가 있는 예외를 서로 구분할 수 있으므로 가독성이 좋아집니다. 예를 들어 문자열에서 JSON 딕셔너리 데이터를 읽어온 후 어떤 키에 해당하는 값을 반환하고 싶을 때 성공적으로 실행되는 경우 try 블록 안에서 json 파일을 디코딩한 후 else 블록 안에서 키 검색을 수행합니다.

import json

def load_json_key(data, key):
    try:
        print('JSON 데이터 읽기')
        result_dict = json.loads(data)
    except ValueError as e:
        print('ValueError 처리')
        raise KeyError(key) from e
    else:
        print('키 검색')
        return result_dict[key]

if __name__ == '__main__':
    assert load_json_key('{"foo": "bar"}', 'foo') == 'bar'

입력이 올바른 json 형식이 아니라면 json.loads가 디코딩하는 중간에 ValueError를 발생시키며 except 블록이 수행되며 else 블록은 수행되지 않습니다.

if __name__ == '__main__':
    load_json_key('{"foo": ', 'foo')

 

try/except/else/finally

try/except/else/finally 블록을 모두 함께 사용할 수 있습니다. 예를 들어 수행할 작업에 대한 설명을 파일에서 읽어 처리한 다음 원본 파일 자체를 변경하고 싶은 경우 try 블록을 사용해 파일을 읽고 처리하고 try 블록 안에서 발생할 것으로 예상되는 예외들은 except 블록을 사용합니다. else 블록을 사용해 원본 파일의 내용을 변경하고 이 과정에서 오류가 생기면 호출한 쪽에 예뢰를 돌려줍니다. finally 블록은 파일 핸들을 닫습니다.

import json

def divide_json(path):
    print('파일 열기')
    handle = open(path, 'r+')
    try:
        print('데이터 읽기')
        data = handle.read()
        print('JSON 데이터 읽기')
        op = json.loads(data)
        print('계산 수행')
        value = (op['numerator'] / op['denominator'])
    except ZeroDivisionError as e:
        print('ZeroDivisionError 처리')
        return object()
    else:
        print('계산 결과 쓰기')
        op['result'] = value
        result = json.dumps(op)
        handle.seek(0)
        handle.write(result)
        return value
    finally:
        print('close() 호출')
        handle.close()

if __name__ == '__main__':
    with open('random_data.json', 'w') as f:
        f.write('{"numerator": 1, "denominator": 10}')
    assert divide_json('random_data.json') == 0.1

정상적인 경우 try -> else -> finally 블록이 실행됩니다.

계산이 잘못된 경우 try, except, finally 블록은 실행되지만 else 블록은 실행되지 않습니다.

if __name__ == '__main__':
    with open('random_data.json', 'w') as f:
        f.write('{"numerator": 1, "denominator": 0}')
    assert divide_json('random_data.json') == 0.1

Json 데이터가 잘못된 경우에는 try 블록이 실행되 예외가 발생하고 finally 블록이 실행됩니다. 하지만 ZeroDivisionError 가 아니므로 except 블록은 실행되지 않고 예외가 발생했으므로 else 블록 또한 실행되지 않습니다.

if __name__ == '__main__':
    with open('random_data.json', 'w') as f:
        f.write('{"numerator": 1 bad}')
    assert divide_json('random_data.json') == 0.1

만약 else 블록에서 예외가 발생한다면 finally 블록이 실행되 파일 핸들을 닫아주면서 예외가 호출자로 전달됩니다.

반응형

'Computer > Python' 카테고리의 다른 글

Numba (1)  (0) 2021.06.02
Pycharm - Python Interpreter  (0) 2021.05.11
Mutable Default Arguments  (0) 2021.04.29
Pycharm 설치  (0) 2021.04.28
itertools 모듈  (0) 2021.03.02