Loading the catalog…
Loading the catalog…
파이썬 예외처리 제대로 이해하기 코드를 짜다 보면 생각지도 못한 곳에서 프로그램이 멈추는 일이 생긴다. 숫자가 들어올 줄 알았던 자리에 문자열이 들어오기도 하고, 있어야 할 파일이 없기도 하다. 예외처리는 이런 상황에서 프로그램이 그냥 죽어버리지 않고 내가 정한 방식대로 대응하게 만드는 장치다. 이번 글에서는 기본 문법부터 예외 계층, 사용자 정의 예외, 그리고 3.11 이후에 추가된 기능까지 순서대로 정리해 봤다. 1. 예외와 문법 오류는 다르다 먼저 헷갈리기 쉬운 부분부터 짚고 넘어가자. 문법 오류(SyntaxError)는 코드를 해석하는 단계에서 걸린다. 실행 자체가 안 되기 때문에 예외처리로 잡을 수 없다. 예외는 문법은 맞는데 실행 중에 문제가 생기는 경우다. 10 / 0 을 하면 ZeroDivisionError, int("abc") 를 하면 ValueError, 없는 파일을 열면 FileNotFoundError가 난다. 예외가 발생하면 파이썬은 호출 스택을 거슬러 올라가면서 이걸 처리해 줄 except 를 찾는다. 끝까지 못 찾으면 프로그램이 종료되고 우리가 흔히 보는 Traceback이 찍힌다. 2. 기본 구조와 실행 흐름 try: value = int(user_input) # 예외가 날 수 있는 코드 except ValueError as e: print(f"숫자가 아닙니다: {e}") # 해당 예외가 났을 때 else: print(f"변환 성공: {value}") # 예외가 없을 때만 실행 finally: print("항상 실행") # 성공이든 실패든 실행 블록별로 하는 일은 이렇다. try : 감시할 코드. 범위는 최대한 좁게 잡는 게 좋다. except : 특정 예외를 처리한다. 여러 개를 둘 수 있고 위에서부터 차례로 검사한다. else : try가 성공했을 때만 실행된다. 성공 이후의 작업을 try 밖으로 빼두면, 원래 잡으려던 게 아닌 예외까지 같이 잡히는 걸 막을 수 있다. finally : 파일 닫기나 DB 연결 해제 같은 정리 작업을 둔다. 중간에 return 이 있어도 실행된다. 여러 예외를 한 번에 잡고 싶으면 튜플로 묶으면 된다. except (ValueError, TypeError) as e: ... 3. 예외 계층 구조를 알아야 하는 이유 파이썬의 예외는 클래스 상속 구조로 되어 있다. BaseException ├── SystemExit, KeyboardInterrupt, GeneratorExit └── Exception ├── ArithmeticError → ZeroDivisionError ├── LookupError → KeyError, IndexError ├── OSError → FileNotFoundError, PermissionError └── ValueError, TypeError ... 이 구조를 알면 자연스럽게 따라오는 원칙이 몇 가지 있다. 첫째, 구체적인 예외를 위에 쓴다. except Exception 을 먼저 써버리면 그 아래에 있는 except KeyError 는 영원히 실행되지 않는다. 둘째, 아무것도 지정하지 않은 except: 는 쓰지 않는다. 이렇게 하면 KeyboardInterrupt 까지 잡혀서 Ctrl+C로도 프로그램이 안 꺼지는 상황이 생긴다. 셋째, except Exception: pass 처럼 예외를 그냥 삼키지 않는다. 당장은 조용해서 좋아 보여도, 나중에 버그가 어디서 났는지 찾을 방법이 사라진다. 4. 예외를 직접 발생시키기: raise와 예외 체이닝 조건에 맞지 않을 때 직접 예외를 던질 수도 있다. def withdraw(balance, amount): if amount > balance: raise ValueError("잔액이 부족합니다") return balance - amount 어떤 예외를 잡아서 더 의미 있는 예외로 바꿔 던지고 싶을 때는 raise ... from ... 을 쓴다. try: price = float(row["close"]) except (KeyError, ValueError) as e: raise DataParseError(f"종가 파싱 실패: {row}") from e from e 를 붙여두면 원래 원인이 Traceback에 같이 남기 때문에 왜 실패했는지 추적하기가 훨씬 쉽다. 원인을 일부러 숨기고 싶다면 from None 을 쓰면 된다. 잡은 예외를 손대지 않고 그대로 다시 던질 때는 인자 없이 raise 만 쓴다. 5. 사용자 정의 예외 내 코드의 상황에 맞는 예외를 직접 만들어 두면 코드가 무슨 의도로 쓰였는지 훨씬 잘 드러난다. class DataPipelineError(Exception): """파이프라인 공통 상위 예외""" class DataParseError(DataPipelineError): pass class MissingDataError(DataPipelineError): pass 공통 상위 클래스를 하나 두는 게 포인트다. 이렇게 하면 호출하는 쪽에서 except DataPipelineError 로 한꺼번에 잡을 수도 있고, 필요하면 세부 예외만 골라서 잡을 수도 있다. 라이브러리들이 많이 쓰는 방식이기도 하다. 6. with문으로 정리 작업 맡기기 try/finally 로 직접 자원을 닫는 대신 with 를 쓰면 예외가 나더라도 알아서 정리된다. with open("prices.csv", encoding="utf-8") as f: data = f.read() # 블록을 빠져나오면 예외가 났든 안 났든 파일은 닫혀 있다 내부적으로는 __exit__ 메서드가 예외 정보를 넘겨받아 정리를 처리한다. 직접 만들어 쓰고 싶다면 contextlib.contextmanager 데코레이터가 편하다. 특정 예외는 그냥 무시해도 되는 상황이라면 contextlib.suppress(FileNotFoundError) 처럼 쓰는 것도 깔끔하다. 7. EAFP vs LBYL 예외를 다루는 스타일에는 크게 두 가지가 있다. 방식 의미 예시 LBYL (Look Before You Leap) 먼저 확인하고 실행 if key in d: x = d[key] EAFP (Easier to Ask Forgiveness than Permission) 일단 해보고 실패하면 처리 try: x = d[key] / except KeyError: 파이썬에서는 전통적으로 EAFP를 더 선호한다. 확인하고 실행하는 사이에 상태가 바뀌는 문제를 피할 수 있기 때문이다. 예를 들어 파일이 있는지 확인한 직후에 다른 프로세스가 그 파일을 지워버리면 확인한 의미가 없어진다. 다만 예외가 아주 자주 발생하는 상황이라면 예외 처리 비용이 쌓이기 때문에 LBYL이 오히려 빠를 수 있다. 결국 상황을 보고 고르면 된다. 8. Python 3.11부터 추가된 기능 ExceptionGroup과 except* 비동기 작업처럼 여러 예외가 동시에 터질 수 있는 상황을 위해 생겼다. try: raise ExceptionGroup("여러 오류", [ValueError("a"), KeyError("b")]) except* ValueError as eg: print("ValueError 처리:", eg.exceptions) except* KeyError as eg: print("KeyError 처리:", eg.exceptions) 일반 except 와 달리 except* 는 그룹 안에서 해당하는 예외만 골라서 처리하고, 나머지는 다음 except* 로 넘긴다. add_note() 예외에 맥락 정보를 덧붙일 수 있다. Traceback을 볼 때 어느 데이터에서 문제가 났는지 바로 알 수 있어서 꽤 유용하다. except ValueError as e: e.add_note(f"문제 행 번호: {i}") raise 9. 실무에서 기억해 둘 것들 마지막으로 실제로 코드를 짤 때 챙기면 좋은 것들을 모아봤다. try 블록에는 예외가 날 만한 줄만 넣는다. 처리할 수 있는 예외만 잡고, 처리할 수 없으면 다시 던진다. 로그는 logging.exception("메시지") 로 남기면 Traceback까지 같이 기록된다. 데이터를 반복 처리할 때는 한 행의 실패 때문에 전체가 멈추지 않도록 행 단위로 잡고, 실패한 건 따로 모아둔다. 정리 작업은 finally 보다 with 를 먼저 떠올린다. 4번은 결측치나 이상값이 섞인 대량 데이터를 다룰 때 특히 쓸모가 많다. errors = [] for i, row in enumerate(rows): try: process(row) except DataPipelineError as e: errors.append((i, str(e))) print(f"실패 {len(errors)}건") 마치며 예외처리는 단순히 에러 메시지를 숨기는 기술이 아니라, 실패했을 때 프로그램이 어떻게 행동할지를 설계하는 일이다. 어떤 예외를 잡고 어떤 예외는 위로 올려보낼지, 실패한 정보를 어떻게 남길지를 고민하다 보면 코드가 한결 단단해진다. 참고 자료 Python 공식 튜토리얼 - Errors and Exceptions Python 공식 문서 - Built-in Exceptions PEP 654 - Exception Groups and except* PEP 3134 - Exception Chaining and Embedded Tracebacks
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
[Python] 예외처리 제대로 이해하기. 파이썬 예외처리 제대로 이해하기 코드를 짜다 보면 생각지도 못한 곳에서 프로그램이 멈추는 일이 생긴다. 숫자가 들어올 줄 알았던 자리에 문자열이 들어오기도 하고, 있어야 할 파일이 없기도 하다. 예외처리는 이런 상황에서 프로그램이 그냥 죽어버리지 않고 내가 정한 방식대로 대응하게 만드는 장치다. 이번 글에서는 기본 문법부터 예외 계층, 사용자 정의 예외, 그리고 3.11 이후에 추가된 기능까지 순서대로 정리해 봤다. 1. 예외와 문법 오류는 다르다 먼저 헷갈리기 쉬운 부분부터 짚고 넘어가자. 문법 오류(SyntaxError)는 코드를 해석하는 단계에서 걸린다. 실행 자체가 안 되기 때문에 예외처리로 잡을 수 없다. 예외는 문법은 맞는데 실행 중에 문제가 생기는…
Open source