Skip to content

Latest commit

 

History

History
166 lines (135 loc) · 4.7 KB

BetterWay65.md

File metadata and controls

166 lines (135 loc) · 4.7 KB

65. try/except/else/finally 의 각 블록을 잘 활용하라

1. finally 블록

  • 예외를 호출 스택의 위로 전달해야 하지만 예외가 발생하더라도 정리 코드를 실행해야 하는 경우 사용
def try_finally_example(filename):
    print('* 파일 열기')
    handle = open(filename, encoding='utf-8') # OSError가 발생할 수 있음
    try:
        print('* 데이터 읽기')
        return handle.read()    # UnicodeDecodeError가 발생할 수 있음
    finally:
        print('* close() 호출')
        handle.close()          # try 블록이 실행된 다음에는 항상 이 블록이 실행됨
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)

>>>
* 파일 열기
* 데이터 읽기
* close() 호출
Traceback ...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1 in position 0: invalid continuation byte
  • read 메소드에서 예외가 발생하면 try_finally_example 을 호출한 쪽으로 예외 전달
  • 그 전에 finally 가 있다면 해당 블록에 있는 handle.close() 호출이 먼저

2. else 블록

  • 코드에서 처리할 예외와 호출 스택을 거슬로 올라가며 전달할 예외를 명확히 구분하기 위해 try/catch/else 사용할 것
  • try 블록에서 예외가 발생하지 않으면 else 블록 실행
    • try 블록 최소화 가능
import json

def load_json_key(data, key):
    try:
        print('* JSON 데이터 읽기')
        result_dict = json.loads(data)    # ValueError가 발생할 수 있음
    except ValueError as e:
        print('* ValueError 처리')
        raise KeyError(key) from e
    else:
        print('* 키 검색')
        return result_dict[key]           # KeyError가 발생할 수 있음
assert load_json_key('{"foo": "bar"}', 'foo') == 'bar'

>>>
* JSON 데이터 읽기
*  검색
load_json_key('{"foo": bad payload', 'foo')

>>>
* JSON 데이터 읽기
* ValueError 처리
Traceback ...           
JSONDecodeError: Expecting value: line 1 column 9 (char 8)
The above exception was the direct cause of the following exception:

Traceback ...
KeyError: 'foo
  • 키 검색에서 예외가 발생하는 경우 try 블록 외부이므로 호출자에게 예외 전달

3. 모든 요소를 한꺼번에 사용하기

UNDEFINED = object()

def divide_json(path):
    print('* 파일 열기')
    handle = open(path, 'r+')        # OSError가 발생할 수 있음
    try:
        print('* 데이터 읽기')
        data = handle.read()         # UnicodeDecodeError가 발생할 수 있음
        print('* JSON 데이터 읽기')
        op = json.loads(data)        # ValueError가 발생할 수 있음
        print('* 계산 수행')
        value = (
            op['numerator'] /
            op['denominator'])       # ZeroDivisionError가 발생할 수 있음
    except ZeroDivisionError as e:
        print('* ZeroDivisionError 처리')
        return UNDEFINED
    else:
        print('* 계산 결과 쓰기')
        op['result'] = value
        result = json.dumps(op)
        handle.seek(0)                    # OSError가 발생할 수 있음
        handle.write(result)              # OSError가 발생할 수 있음
        return value
    finally:
        print('* close() 호출')
        handle.close()                    # 어떤 경우든 실행됨
temp_path = 'random_data.json'

with open(temp_path, 'w') as f:
    f.write('{"numerator": 1, "denominator": 10}')
assert divide_json(temp_path) == 0.1

>>>
* 파일 열기
* 데이터 읽기
* JSON 데이터 읽기
* 계산 수행
* 계산 결과 쓰기
* close() 호출
with open(temp_path, 'w') as f:
    f.write('{"numerator": 1, "denominator": 0}')

assert divide_json(temp_path) is UNDEFINED

>>>
* 파일 열기
* 데이터 읽기
* JSON 데이터 읽기
* 계산 수행
* ZeroDivisionError 처리
* close() 호출
with open(temp_path, 'w') as f:
    f.write('{"numerator": 1, "denominator": 10}')
    
divide_json(temp_path)

>>>
* 파일 열기
* 데이터 읽기
* JSON 데이터 읽기
* 계산 수행
* 계산 결과 쓰기
* close() 호출
Traceback ...
OSError: [Errno 28] No space left on device
  • 정상적인 경우 try, else, finally 블록 실행
  • 계산이 잘못된 경우 try, except, finally 블록 실행, else 블록 실행 X
  • JSON 데이터가 잘못된 경우 try 블록 실행, 예외 발생, finally 블록 실행, excpet, else 블록 실행 X
  • else 에서 결과 데이터를 파일에 쓰는 동안 예외 발생하면 finally 블록 실행, 핸들 닫음