우리는 프로그램 실행 중에 발행하는 에러는 미리 방지하기 위해 예외처리를 사용한다.

try-except

예외 처리를 위해서 try-except 구문을 사용하며 else 구문은 자주 사용하지 않는다. finally는 파일을 닫아주는 것과 같이 사용한 리소스를 무조건 반환해야 하는 코드가 필요할 때 사용한다.

try-except는 프로그램을 종료하지 않는다는 특성이 있다.

try:
  예외가 발생할  있는 코드
except:
  예외 발생  실행할 코드
else:
  예외 발생하지 않은 경우 실행할 코드
finally:
  항상 실행할 코드

예시로 원화와 환율을 입력 받아 달러값을 반환하는 함수를 만들어 본다.

won = input('원화 금액을 입력하시오.')
dollar = input('환율을 입력하시오.')

try:
    print(int(won)/int(dollar))
except:
    print('예외가 발생함')

아래와 같이 발생하는 에러를 직접 지정해 줄 수도 있다. e라는 별칭을 주고 print를 하면 에러 메세지도 같이 출력할 수 있다.

won = input('원화 금액을 입력하시오.')
dollar = input('환율을 입력하시오.')

try:
    print(int(won)/int(dollar))
except ValueError as e:
    print('문자열 예외가 발생함', e)
except ZeroDivisionError:
    print('나눗셈의 분모는 0이 불가능함')
else: 
    print('정상적으로 계산되었습니다.')
finally:
    print('항상 실행되는 코드')

예외 계층 구조

아래 예외 계층을 참고하여 try-except 구문을 사용하면 된다. 예를 들어 except OSError 를 사용하면 OSError 하위에 있는 ChildProcessError, FileExistsError,… TimeoutError 가 일어 났을때 모두 커버가 가능하다. 하지만 어떤 예외가 발생하는지 아는 상황에서는 상세히 예외를 적어주는 것이 좋다.

image


에러 발생시키기

raise 구문은 에러를 강제로 발생시킬 때 사용한다.

raise 예외이름('에러메세지')

에러 메세지는 옵션이다.

num = int(input('음수를 입력하세요.'))
if num >= 0:
    raise Exception('양수 입력 불가')
    
결과
Exception: 양수 입력 불가
try:
    num = int(input('음수를 입력하세요.'))
    if num >= 0:
        raise ValueError('양수 입력 불가')
except ValueError as e: 
    print('에러 발생', e)
    
결과
에러 발생 양수 입력 불가

예외 만들기

예외를 직접 만들 때에는 Exception 클래스를 상속받아 새로운 클래스를 만들면 된다.

class 예외이름(Exception):
  def __init__(self):
    super().__init__('에러메세지')
class PositiveNumberError(Exception):
    def __init__(self):
        super().__init__('양수 입력 불가')

try:
    num = int(input('음수를 입력하세요.'))
    if num >= 0:
        raise PositiveNumberError
except PositiveNumberError as e: 
    print('에러 발생!', e)

결과
에러 발생! 양수 입력 불가

NotImplementedError

NotImplementedError는 클래스 상속에 관련된 에러이다. 상위 클래스를 설계할 때 하위 클래스에서 반드시 오버라이드하여 상세하게 구현해야 하는 메소드를 명시하고자 하려면, 해당 메소드의 내용으로 raise NotImplementedError(메시지)를 넣어놓는다.

class BaseWheel:
    def roll(self):
        raise NotImplementedError('roll 메소드를 구현하세요.')

class MyWheel(BaseWheel):
    pass

이렇게 자식클래스를 설계할 때 roll 메소드를 오버라이딩 하지 않으면 NotImplementedError가 발생한다.

>>> mywheel = MyWheel()
>>> mywheel.roll()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in roll
NotImplementedError: roll 메소드를 구현하세요.

하지만 아래와 같이 코드를 수정하면 정상적으로 인스턴스를 생성하고 메서드를 불러올 수 있다.

class BaseWheel:
    def roll(self):
        raise NotImplementedError('roll 메소드를 구현하세요.')

class MyWheel(BaseWheel):
    def roll(self):
        print('빠르게 굴러가는 바퀴')
>>> mywheel = MyWheel()
>>> mywheel.roll()
빠르게 굴러가는 바퀴