본문 바로가기
IT/파이썬 기초 완전 정복

파이썬 예외 처리(try, except) 방법 (9)

by 지식 발전소 2024. 4. 20.
728x90
반응형

 

예외(Exception)란 무엇인가?

 

 

안녕하세요. 이번에는 파이썬 프로그래밍에서 매우 중요한 예외 처리에 대해 자세히 알아보겠습니다. 예외 처리는 프로그램 실행 중 발생할 수 있는 예외 상황을 적절히 처리하는 방법입니다. 예외 처리를 제대로 하지 않으면 프로그램이 예기치 않게 중단되어 사용자 경험이 나빠지거나 심각한 경우 데이터 손실이 발생할 수 있습니다.

 

예외(Exception)란 무엇일까요? 예외는 프로그램 실행 중 발생하는 오류 상황을 말합니다. 예를 들어 0으로 나누려고 하거나, 없는 파일을 열려고 하거나, 네트워크 연결 오류 등이 예외 상황에 해당됩니다. 이런 상황이 발생하면 프로그램이 정상 실행되지 않고 중단됩니다.

 

프로그래밍을 할 때 모든 예외 상황을 미리 대비하기는 어렵습니다. 하지만 예외 처리를 통해 예외 상황이 발생해도 프로그램이 비정상 종료되지 않고 안전하게 실행될 수 있습니다. 또한 예외 메시지를 잡아내어 로그로 기록하거나 사용자에게 오류 정보를 제공할 수도 있습니다.

 

try/except 구문으로 예외 처리하기

파이썬에서는 try/except 구문을 사용하여 예외를 처리합니다. 기본 문법은 다음과 같습니다.

try:
    # 예외 발생 가능 코드
except 예외이름1:
    # 예외이름1이 발생했을 때 실행될 코드
except 예외이름2:
    # 예외이름2가 발생했을 때 실행될 코드
except:
    # 어떤 예외라도 발생했을 때 실행될 코드
else:
    # 예외가 발생하지 않았을 때 실행될 코드
finally:
    # 예외 발생 여부와 상관없이 항상 실행될 코드

 

try 블록에는 예외가 발생할 가능성이 있는 코드를 넣습니다. 예외가 발생하지 않으면 except 블록은 무시되고, else 블록의 코드가 실행됩니다. 만약 예외가 발생하면 해당 예외 종류의 except 블록이 실행됩니다. except에 예외 이름을 지정하지 않으면 모든 예외에 대해 동일한 처리를 합니다.

finally 블록은 예외 발생 여부와 관계없이 항상 실행됩니다. 주로 리소스 반환, 로그 기록 등의 마무리 작업에 사용합니다.

예시 1) 0으로 나누기 예외 처리

try:
    result = 10 / 0  # 예외 발생 코드
except ZeroDivisionError:
    print("0으로 나눌 수 없습니다.")
else:
    print(result)

위 코드에서 10 / 0은 0으로 나누기 때문에 ZeroDivisionError 예외가 발생합니다. 이 예외를 except ZeroDivisionError 블록에서 처리하고 "0으로 나눌 수 없습니다."라는 메시지를 출력합니다.

예시 2) 여러 예외 동시 처리

try:
    값 = int(input("정수를 입력하세요: "))
    print(20 / 값)
except (ValueError, ZeroDivisionError) as e:
    print(f"오류 발생: {e}")

위 코드는 사용자에게 정수를 입력받습니다. 만약 정수가 아닌 값을 입력하면 ValueError 예외가, 0을 입력하면 ZeroDivisionError 예외가 발생합니다. 두 예외를 하나의 except 블록에서 동시에 처리합니다.

왜 예외 처리가 중요할까요? 예외 처리를 하지 않으면 프로그램이 예기치 않게 중단되어 사용자가 불편함을 겪게 됩니다. 또한 로그 기록이 없어 오류 원인을 찾기 어려워집니다. 예외 처리를 제대로 하면 프로그램의 안정성이 높아지고, 효과적인 오류 메시지를 사용자에게 제공할 수 있습니다.

예외 구문에서 예외 객체 다루기

except 블록에서 예외 인스턴스에 접근할 수 있습니다. 이를 통해 예외에 대한 자세한 정보를 얻을 수 있습니다.

try:
    값 = int(input("정수를 입력하세요: "))
    print(20 / 값)
except ValueError as e:
    print(f"값에 정수가 아닌 데이터가 입력되었습니다: {e}")
except ZeroDivisionError as e:
    print(f"0으로 나눌 수 없습니다: {e}")

위 코드에서 e는 예외 인스턴스입니다. e를 출력하면 예외 메시지를 볼 수 있습니다. 또한 예외 인스턴스의 속성에 접근하여 자세한 정보를 확인할 수 있습니다.

  • e.args: 예외가 발생한 원인에 대한 설명이 튜플로 저장되어 있습니다.
  • e.__cause__: 현재 예외의 원인이 되는 예외가 저장되어 있습니다.
  • e.__context__: 현재 예외가 발생한 컨텍스트 정보가 저장되어 있습니다.

예외 발생시키기 - raise

때로는 강제로 예외를 발생시켜야 하는 경우가 있습니다. 이때 raise 명령어를 사용합니다.

def 나이_확인(나이):
    if 나이 < 0:
        raise ValueError("나이는 0보다 작을 수 없습니다.")
    elif 나이 > 200:
        raise ValueError("200세 이상은 비현실적입니다.")
    else:
        print(f"{나이}세는 정상 범위입니다.")

나이_확인(25)  # 정상 출력
나이_확인(-5)  # ValueError 발생
나이_확인(250)  # ValueError 발생

위 예시에서는 나이_확인() 함수 내부에서 raise ValueError()를 통해 직접 예외를 발생시킵니다. 이 때 예외 메시지도 함께 지정할 수 있습니다.

raise를 이용하면 어떤 이점이 있을까요? 입력값에 대한 유효성 검사 등 사전에 프로그램 동작을 제한할 수 있습니다. 이를 통해 의도치 않은 예외 상황을 미리 방지할 수 있습니다. 또한 고의로 예외를 발생시켜 프로그램 실행 흐름을 제어할 수도 있습니다.

파이썬 내장 예외 종류

파이썬에는 다양한 예외 종류가 내장되어 있습니다. 가장 많이 사용되는 예외 종류는 다음과 같습니다.

  • Exception: 모든 예외의 기본 클래스입니다.
  • ArithmeticError: 수치 계산 오류가 발생한 경우 발생합니다.
    • ZeroDivisionError: 0으로 나누려고 할 때 발생합니다.
  • LookupError: 시퀀스나 매핑 타입에서 인덱싱 또는 키가 잘못된 경우 발생합니다.
    • IndexError: 인덱스가 범위를 벗어났을 때 발생합니다.
    • KeyError: 매핑 타입에서 존재하지 않는 키에 접근할 때 발생합니다.
  • ValueError: 잘못된 값을 할당하려고 할 때 발생합니다.
  • TypeError: 연산 대상들의 타입이 잘못되었을 때 발생합니다.
  • NameError: 정의되지 않은 변수나 함수를 참조할 때 발생합니다.
  • FileNotFoundError: 존재하지 않는 파일을 열려고 할 때 발생합니다.
  • ImportError: 모듈을 불러오는 데 실패했을 때 발생합니다.

위 외에도 다양한 예외 종류가 있습니다. 예외 처리 시 적절한 예외 종류를 지정하면 더욱 효과적으로 예외를 처리할 수 있습니다.

# IndexError 예시
fruits = ['apple', 'banana', 'orange']
try:
    print(fruits[3])  # 인덱스 범위를 벗어남
except IndexError as e:
    print(f"인덱스 범위를 벗어났습니다: {e}")

예외 계층 구조 이해하기

파이썬 예외는 계층적 구조를 가지고 있습니다. 예외 계층 구조는 다음 이미지와 같습니다.

이미지 출처: Real Python

 

Python Exceptions: An Introduction – Real Python

In this beginner tutorial, you'll learn what exceptions are good for in Python. You'll see how to raise exceptions and how to handle them with try ... except blocks.

realpython.com

Exception 클래스가 가장 상위 클래스이며, 특정 예외 상황에 맞는 예외 클래스가 하위에 위치합니다. 예를 들어 ArithmeticError는 Exception의 하위 클래스이며, ZeroDivisionError는 ArithmeticError의 하위 클래스입니다.

예외 계층 구조를 이해하면 예외 처리 시 더욱 효과적으로 예외를 잡아낼 수 있습니다. 상위 클래스의 예외를 지정하면 하위 예외까지 모두 처리할 수 있습니다.

try:
    값 = int(input("정수를 입력하세요: "))
    print(20 / 값)
except ArithmeticError as e:
    print(f"산술 오류가 발생했습니다: {e}")

위 코드에서는 ArithmeticError를 지정하여 0으로 나누기와 같은 산술 오류뿐만 아니라 다른 산술 오류도 한꺼번에 처리할 수 있습니다.

 

하지만 상위 클래스 예외를 잡으면 세밀한 예외 처리가 불가능해질 수 있습니다. 따라서 상황에 맞게 적절한 수준의 예외 클래스를 선택하는 것이 좋습니다.

예외 발생 추적하기 - traceback

예외가 발생하면 파이썬은 예외 발생 지점과 예외 메시지를 출력합니다. 이러한 예외 정보를 추적하려면 traceback 모듈을 사용합니다.

import traceback

try:
    값 = int(input("정수를 입력하세요: "))
    print(20 / 값)
except Exception as e:
    print("예외 발생!")
    print(e)
    print(traceback.format_exc())  # traceback 출력

위 코드에서 traceback.format_exc()를 호출하면 예외가 발생한 라인, 예외 유형, 메시지 등의 상세한 추적 정보를 출력합니다. 이 정보를 통해 예외 발생 원인을 쉽게 파악할 수 있습니다.

traceback 모듈에는 다른 유용한 함수도 있습니다.

  • traceback.print_exc(): 추적 정보를 콘솔에 직접 출력합니다.
  • traceback.format_tb(): 추적 정보 중 실행 스택만 반환합니다.
  • traceback.extract_tb(): 추적 정보의 프레임 객체 리스트를 반환합니다.

프로그램 개발 시 traceback 모듈을 활용하면 오류를 빠르게 진단하고 수정할 수 있습니다.

사용자 정의 예외 만들기

내장 예외 외에도 프로그래머가 직접 예외 클래스를 정의할 수 있습니다. 이를 통해 프로그램의 특정 상황에 맞는 예외를 만들 수 있습니다.

사용자 정의 예외를 만드는 방법은 Exception 클래스를 상속받아 새로운 예외 클래스를 정의하는 것입니다.

class 값오류(Exception):
    """값이 특정 범위를 벗어났을 때 발생하는 예외"""
    def __init__(self, 메시지, 값):
        self.메시지 = 메시지
        self.값 = 값
        super().__init__(메시지)

    def __str__(self):
        return f"{self.메시지}: {self.값}"

def 값_확인(값):
    if 값 < 0 or 값 > 100:
        raise 값오류(f"{값}은(는) 0과 100 사이의 값이 아닙니다", 값)
    else:
        print(f"{값}은 정상 범위입니다.")

값_확인(200)  # 값오류 발생
값_확인(50)   # 정상 출력

위 예제에서 값오류 클래스를 직접 정의했습니다. Exception을 상속받아 커스텀 예외 메시지와 예외 발생 시 전달할 값을 처리할 수 있습니다.

__init__ 메서드에서 예외 메시지와 값을 인수로 받아 인스턴스 변수에 저장합니다. __str__ 메서드를 정의하여 예외 인스턴스를 문자열로 반환할 때의 형식을 지정했습니다.

값_확인 함수에서 raise 값오류(...)를 통해 직접 정의한 예외를 발생시킵니다.

사용자 정의 예외를 만드는 이유는 무엇일까요? 프로그램의 특정 상황에 맞는 예외를 정의함으로써 예외 처리 코드의 의미를 더욱 명확히 할 수 있습니다. 또한 예외 객체에 프로그램 실행 정보를 담아 디버깅에 활용할 수도 있습니다.

예외 처리 패턴: EAFP vs LBYL

예외 처리에는 크게 두 가지 패턴이 있습니다.

  1. LBYL(Look Before You Leap): 코드를 실행하기 전에 예외 발생 가능성을 미리 확인합니다.
  2. EAFP(Easier to Ask for Forgiveness than Permission): 예외가 발생하면 그때 처리하는 방식입니다.

각 패턴의 장단점이 있으므로 상황에 맞게 적절히 사용해야 합니다.

LBYL 예시

가수 = {"아이유": 28, "김종국": 46}

이름 = input("가수 이름을 입력하세요: ")
if 이름 in 가수:
    print(f"{이름}의 나이는 {가수[이름]}살입니다.")
else:
    print(f"{이름}은(는) 없는 가수입니다.")

위 코드는 LBYL 패턴으로 작성되었습니다. 가수 이름을 입력받기 전에 딕셔너리에 이름이 있는지 먼저 확인합니다. 이렇게 하면 KeyError 예외가 발생하지 않습니다.

EAFP 예시

가수 = {"아이유": 28, "김종국": 46}

try:
    이름 = input("가수 이름을 입력하세요: ")
    print(f"{이름}의 나이는 {가수[이름]}살입니다.")
except KeyError:
    print(f"{이름}은(는) 없는 가수입니다.")

위 코드는 EAFP 패턴으로 작성되었습니다. 먼저 딕셔너리 접근을 시도하고, KeyError가 발생하면 그때 예외를 처리합니다.

LBYL은 예외 발생 가능성을 사전에 걸러내므로 예외 처리 오버헤드가 적습니다. 하지만 조건문이 많아져 가독성이 나빠질 수 있습니다.

EAFP는 예외 처리 로직이 단순해 가독성이 좋지만, 예외 발생 시 오버헤드가 큽니다. 파이썬 철학인 "용서를 구하는 것이 허가를 구하는 것보다 쉽다"에 부합하는 패턴입니다.

두 패턴의 장단점을 고려하여 상황에 맞게 적절히 사용하는 것이 좋습니다. 파이썬에서는 일반적으로 EAFP 패턴을 더 많이 사용합니다.

파일 및 자원 처리에서 예외 처리 활용

파일 처리나 외부 자원 사용 시에는 예외 처리가 필수적입니다. 예상치 못한 상황으로 인해 파일이 없거나 네트워크에 문제가 생기는 등의 예외가 발생할 수 있기 때문입니다.

try:
    파일 = open("없는파일.txt", "r")
except FileNotFoundError as e:
    print(f"파일을 찾을 수 없습니다: {e}")
else:
    print("파일을 성공적으로 열었습니다.")
    파일.close()

위 코드는 존재하지 않는 파일을 열려고 시도합니다. FileNotFoundError 예외가 발생하면 except 블록에서 이를 처리합니다.

try/finally 구문을 사용하면 자원 반환을 안전하게 처리할 수 있습니다.

try:
    파일 = open("파일.txt", "r")
    # 파일 처리 코드
finally:
    파일.close()

finally 블록에서 파일을 닫는 코드를 작성하면, 예외 발생 여부와 상관없이 파일이 안전하게 닫힙니다.

또한 파이썬의 Context Manager를 사용하면 자원 반환 코드를 더욱 간결하게 작성할 수 있습니다.

with open("파일.txt", "r") as 파일:
    # 파일 처리 코드

with 문 안에서 파일을 열면, 블록을 벗어날 때 자동으로 파일이 닫힙니다. 이 방식을 사용하면 try/finally 구문을 직접 작성할 필요가 없습니다.

데이터베이스 연결, 네트워크 소켓, 잠금 기능 등 다양한 자원에서 Context Manager를 활용할 수 있습니다. 이를 통해 안전하고 간결한 코드를 작성할 수 있습니다.

참고 자료

  1. 박응용 (2022), 점프 투 파이썬, https://wikidocs.net/30594
  2. Python 예외 처리 문서, https://docs.python.org/3/tutorial/errors.html
  3. Real Python, Python Exceptions: An Introduction, https://realpython.com/python-exceptions/
  4. Corey Schafer, Python try, except, finally, raise https://youtu.be/ipr7lly4UGM

 

 

한 고대 문서 이야기

여기 한 고대 문서가 있습니다. 이 문서는 B.C. 1,500년 부터 A.D 100년까지 약 1,600 여 년 동안 기록되었습니다. 이 문서의 저자는 약 40 명입니다. 이 문서의 고대 사본은 25,000 개가 넘으나, 사본간 오

gospel79.tistory.com

 

유튜브 프리미엄 월 1만원 할인받고 월 4000원에 이용하는 방법

올해 5월부터 월 8000원 정도이던 유튜브 프리미엄 요금이 15000원 정도로 인상됩니다. 각종 OTT 서비스, ChatGPT 같은 서비스들이 늘어나다보니 이런 거 몇 개만 이용하더라도 월 이용요금이 5만원을

stock79.tistory.com

 

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

728x90
반응형

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

댓글