Programming/effective python

파이썬스러운 예외 처리 방법

알파고라니 2025. 5. 11. 23:59

파이썬에서는 예외를 처리하는 과정에서 특정 동작을 수행하려면 4가지 경우를 고려하여 조합해 사용할 수가 있습니다. 이 구조를 잘 활용하면 예외 처리 로직이 명확해지고, 버그가 줄어들며, 코드 유지보수가 쉬워집니다.

1. 네 가지 예외처리 블록

파이썬에서는 기본적으로 try, except, else, finally의 네 가지 블록을 조합하여 예외 처리를 할 수 있습니다. 각 블록이 가져야 하는 의미는 다음과 같습니다.

  • try: 예외가 발생할 수 있는 코드를 실행하는 블록입니다.
  • except: 예외가 발생했을 때 실행되는 블록입니다.
  • else: 예외가 발생하지 않았을 때만 실행되는 블록입니다. (try가 성공했을 때 실행)
  • finally: 예외 발생 여부와 상관없이 무조건 실행되는 블록입니다. 자원 정리 등에 자주 사용됩니다.

try, except는 예외 처리시에 자주 사용하는 블록입니다. try 블록 안에 처리 코드를 넣고 except에서 예외 처리를 하는 식입니다. else 블록은 예외가 발생하지 않았을 때만 실행되므로  try 블록 안을 최소한으로 유지하고, 정상 실행 로직은 else에 두면 예외 처리와 정상 흐름이 명확히 분리됩니다. 이는 정상적인 흐름을 분리하여 가독성을 높여줍니다. (except와 분리되어 성공 여부 구분 가능)

또한 finally는 파일 닫기, 네트워크 연결 종료 등 정리(clean-up) 작업을 위해 사용됩니다. try가 성공적으로 처리되고 finally 블록이 공통적인 정리 작업을 수행하기 전에 실행할 코드가 있다면 else 블록을 사용하면 됩니다.

 

# 네 가지 예외처리 예시 코드
UNDEFINED = object()

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:
    	print('* 0 나눗셈 예외 처리')
        return UNDEFINED
    else:
    	print('* 정상 계산 결과')
        op['result'] = value
        result = json.dumps(op)
        handle.seek(0)
        handle.write(result)
        return value
     finally:
     	print('* clean up 수행')
        handle.close()

 

코드 예시를 실행시키며 예외가 어디서 호출되는 지 그 경우에 어떤 블록까지 실행되는 지 파악해보면 바로 이해가 될 것입니다.

 

2. contextlib과 with 문을 사용하여 예외처리

예외 처리를 하게 되는 것을 알게 되면 이를 반복적으로 사용하고 싶을 때가 있습니다. 예를 들어 파일을 열고 닫을 때, lock을 걸고 반드시 해제할 때 등이 있습니다. 그럴 때 보통 try 블록에서 특정 기능을 수행하고, finally에서 정리 작업을 반복적으로 사용하게 됩니다.

# lock 설정/해제 예시

from threading import Lock
lock = Lock()
lock.acquire()

try: 
	# 특정 작업 수행
finally:
	# 정리작업
    lock.release()

 

이렇게 반복적으로 사용하다보면 중복 코드도 많아지고, 버그도 찾기 어려워진다는 단점이 있습니다. 그래서 이를 재사용 가능하게 하는 방식이 contextlib과 with문을 사용는 것입니다.

with를 사용하는 경우는 위의 코드 예시에서 acquire에 대응하는 release를 실수로 빠뜨리는 경우를 방지하게 해주며, 반복사용에 용이합니다. 예를 들면 다음과 같습니다.(위의 코드예시와 비교해보세요)

from threading import Lock

lock = Lock()
with lock:
	# 특정 작업(기능) 수행

 

contextlib 내장 모듈을 사용하는 경우는 우리가 만든 객체나 함수를 with 문에 쉽게 쓰기 위함의 경우입니다. contextmanager 데코레이터를 사용하여 복잡한 try/finally 블록을 함수처럼 포장해서 with 문으로 쓸 수 있게 해주는 것입니다. 

 

# 예시 코드
from contextlib import contextmanager

@contextmanager
def my_context():
    # 진입 시 동작
    print("자원 준비")
    try:
        yield "사용할 값"
    finally:
        # 정리(clean-up) 동작
        print("자원 정리")
        
 with my_context() as val:
 	print('내부 실행', val)

 

contextmanager가 yield 하는 값은 with 문의 as 부분에 전달되어, 특별한 컨텍스트 내부에서 실행되는 코드 안에서 직접 그 컨텍스트에 접근할 수 있게 합니다. 이를 활용해서 예외 처리 로직을 반복 처리할 수 있습니다.

'Programming > effective python' 카테고리의 다른 글

파이썬 문자열 다루는 방법 1  (1) 2025.06.30
Pythonic Thinking  (1) 2025.05.05
Effective python 소개  (1) 2025.05.05