본문 바로가기
Python

[Python] 파이썬 예외처리 - try, except, finally, raise

by A6K 2022. 11. 9.

프로그램이 실행되다보면 의도하지 않은 상황을 만나게 되는 경우가 많다. 예를 들어 파일을 오픈해서 데이터를 읽으려고하는데 파일이 존재하지 않는다던가, 어떤 값을 0으로 나누려고 시도했다던가 하는 상황이 발생한다. 파이썬은 이럴 때 에러를 발생시킨다.

예를 들어보자.

f = open('not_exists_file.txt', 'r')

# Traceback (most recent call last):
#  File "./test.py", line 1, in <module>
#     f = open('not_exists_file.txt', 'r')
# FileNotFoundError: [Errno 2] No such file or directory: 'not_exists_file.txt'

존재하지 않는 파일을 열려고 했을 때, 파이썬은 FileNotFoundError를 발생시킨다.

num1 = 10
num2 = 0

result = num1 / num2

# Traceback (most recent call last):
#   File "./test.py", line 7, in <module>
#     result = num1 / num2
# ZeroDivisionError: division by zero

특정 변수를 다른 변수 값으로 나누려고 할 때, 분모에 해당하는 변수 값이 0인 경우가 종종 있다. 이 경우 ZeroDivisionError가 발생한다.

이렇듯 파이썬은 프로그램의 실행이 계속되지 못하는 상황에 에러를 발생시킨다. 그러면서 프로그래머가 이 상황이 발생했을 때, 프로그램을 종료하지 않고 예외처리를 하여 문제의 상황을 좀 더 우아하게 빗겨 갈 수 있도록 예외처리 기능을 제공해준다.

파이썬 예외처리 - try, except

파이썬에서는 try - exception 구문으로 예외처리를 할 수 있다.

try:
  ...
except [에러 [as 변수]]:
  ...

try 블록에 있는 코드를 수행하다가 에러가 발생하면 except 블록으로 넘어가서 except 블록의 코드가 수행된다. try 블록의 코드 수행중 에러가 발생하지 않으면 except 블록의 코드는 실행되지 않는다.

except 블록이 시작되는 부분에는 except 블록이 예외처리할 에러를 적거나 생략할 수 있다. 예외처리 할 에러를 적을 경우 에러 객체를 as 구문으로 특정 변수에 담아 except 블록에서 다룰 수 있다. 물론 이 또한 선택적이어서 as 변수 값을 명시하지 않아도 된다.

예를 들어보자.

try :
  num1 = 10
  num2 = 0
  result = num1 / num2
  print(result)
except :
  print('예외발생')

print('프로그램은 계속 진행')

# 예외발생
# 프로그램은 계속 진행

이 코드를 실행하면 num1을 num2로 나눌 때 분모가 0이므로 ZeroDivisionError가 발생한다. 하지만 문제가 발생할 수 있는 코드를 try - except 구문으로 감싸서 에러가 프로그램을 종료시키지 않고, except 블록을 실행한 다음 정상적으로 진행되도록 했다.

except 구문에 특정 에러를 주지 않으면 발생한 모든 에러에 대해 예외처리를 한다. except 구문에 특정 에러를 명시하면 그 에러가 발생했을 때만 except 블록이 실행된다.

예를 들어보자.

try :
  num1 = 10
  num2 = 0
  result = num1 / num2
  print(result)
except IndexError:
  print('예외발생')

print('프로그램은 계속 진행')

# Traceback (most recent call last):
#   File "./test.py", line 7, in <module>
#     result = num1 / num2
# ZeroDivisionError: division by zero

num1 / num2 부분에서 ZeroDivisionError가 발생한다. 하지만 try - except 구문에서는 IndexError 만을 명시했으므로 ZeroDivisionError는 예외처리가 안된다. 따라서 ZeroDivisionError가 프로그램의 수행을 중단시킨다.

파이썬에서 except 블록은 여러개를 쓸 수 있다. 

try :
  num1 = 10
  num2 = 0
  result = num1 / num2
  print(result)
except IndexError:
  print('IndexError 발생')
except ZeroDivisionError:
  print('ZeroDivisionError 발생')
except :
  print('기타 에러 발생')

print('프로그램은 계속 진행')

# ZeroDivisionError 발생
# 프로그램은 계속 진행

이 코드처럼 try - except 구문을 작성하면, try 블록을 실행하다가 IndexError가 발생하면 IndexError에 해당하는 except 블록이 실행되고, ZeroDivisionError가 발생하면 ZeroDivisionError에 해당하는 except 블록이 실행된다. 그리고 마지막 부분에 에러를 특정하지 않은 except 블록을 명시하면 위에서 명시하지 않은 다른 에러가 발생했을 경우 실행된다.

try-except 구문에서 에러가 발생했을 때, 위에서부터 발생한 에러에 대한 예외처리가 있는지 except 구문을 하나씩 체크해서 예외처리에 해당하는 except 블록이 실행된다.

두 개 이상의 에러가 같은 예외처리 로직으로 처리되도록 설정할 수도 있다.

try :
  num1 = 10
  num2 = 0
  result = num1 / num2
  print(result)
except (IndexError, ZeroDivisionError) :
  print('Error 발생')
except :
  print('기타 에러 발생')

print('프로그램은 계속 진행')

# Error 발생
# 프로그램은 계속 진행

except 구문에서 as 구문으로 발생한 에러 객체를 예외처리 블록에서 사용할 수도 있다.

try :
  num1 = 10
  num2 = 0
  result = num1 / num2
  print(result)
except ZeroDivisionError as e:
  print(e)

# division by zero

파이썬 예외처리 - else

try - except 구문에는 else 블록을 추가할 수도 있다. else 블록은 에러가 발생하지 않았을 때 실행되는 블록이다. else 블록은 except 블록이 반드시 있어야 명시할 수 있다. 마치 if 블록이 있어야 else 블록을 사용할 수 있는 것처럼 말이다.

예를 들어보자.

try:
  num1 = 10
  num2 = 5
  result = num1 / num2
  print(result)
except:
  print('예외발생')
else:
  print('정상종료')

print('프로그램 정상 실행')

# 2.0
# 정상종료
# 프로그램 정상 실행

try 블록에 있는 코드는 정상적으로 수행된다. 그리고 예외가 발생하지 않았으므로 else 블록의 내용이 실행된 다음 try-except 구문이 종료된다.

파이썬 예외처리 - finally

finally 블록은 예외가 발생하던 하지 않던 무조건 실행되는 블록이다. 

예를 들어보자.

try :
  num1 = 10
  num2 = 0
  result = num1 / num2
  print(result)
finally:
  print('finally 블록')

print('프로그램은 계속 진행')

# finally 블록
# Traceback (most recent call last):
#   File "./test.py", line 7, in <module>
#     result = num1 / num2
# ZeroDivisionError: division by zero

try 블록의 코드는 ZeroDivisionError를 발생시킨다. 그리고 이를 처리하는 except 블록이 없기 때문에 ZeroDivisionError는 프로그램을 그대로 종료시킨다. 하지만 주목할 점은 프로그램이 종료되기 전에 finally 블록의 내용이 먼저 실행되었다는 점이다.

finally 블록은 예외처리를 해도 실행된다.

try :
  num1 = 10
  num2 = 0
  result = num1 / num2
  print(result)
except ZeroDivisionError:
  print('예외처리')
finally:
  print('finally 블록')

print('프로그램은 계속 진행')

# 예외처리
# finally 블록
# 프로그램은 계속 진행

예외가 발생했을 때, 예외에 대한 except 블록이 처리 된 이후 마지막으로 finally 블록이 실행된다.

finally 블록은 예외가 발생하지 않아도 실행된다.

try :
  num1 = 10
  num2 = 2
  result = num1 / num2
  print(result)
except ZeroDivisionError:
  print('예외처리')
finally:
  print('finally 블록')

print('프로그램은 계속 진행')

# 5.0
# finally 블록
# 프로그램은 계속 진행

예외가 발생하지 않았지만 finally 블록은 실행되었다.

이처럼 finally 블록은 예외의 발생여부, 예외처리의 여부와 상관없이 무조건 실행된다. 코드에서 DB 연결이나 파일을 열어서 작업을 하는 경우 정상종료, 예외발생시 모두 열린 파일과 DB 연결을 정리해줘야하는데 이런 작업들을 finally 블록에서 해주면 된다.

예외 발생 - raise

파이썬 코드를 작성하다보면 에러를 발생시켜야할 경우가 있다. 파이썬은 raise 키워드를 이용해서 에러를 발생시킬 수 있다. 이렇게 발생시킨 에러는 내가 만든 코드를 가져다 사용하는 쪽에서 try-except 구문으로 예외처리를 할 수 있다.

예를 들어보자.

def division(a, b):
  if b == 0:
    raise ValueError
  return a / b
  
print(division(10, 2))

# Traceback (most recent call last):
#   File "./test.py", line 9, in <module>
#     print(division(10, 0))
#   File "./test.py", line 6, in division
#     raise ValueError
# ValueError

두 숫자를 나누는 함수 division()을 구현했다. 첫 번째 인자를 두 번째 인자로 나누는 동작을 하는데 0으로 나누는 경우를 미리 확인해서 ZeroDivisionError가 아닌 ValueError를 내도록 수정했다.

이 때, raise 문을 이용해서 발생시킬 에러를 명시할 수 있다.

예외 만들기

파이썬으로 프로그래밍을 하다보면 내 프로그램에서 다뤄야 할 예외 상황을 만들어야 하는 경우가 발생한다. 사용자가 정의해서 쓸 수 있는 예외는 다음과 같이 만들 수 있다.

class MyError(Exception):
  pass

사용자가 만드는 예외는 파이썬의 내장 클래스인 Exception을 상속해서 만들 수 있다.

예를 들어보자.

class MyError(Exception):
    def __init__(self, a):
        self.a = a
    def get_value(self):
        return self.a

def even_value(a):
    if a % 2 == 1:
        raise MyError(a)
    
try:
    even_value(2)
    even_value(4)
    even_value(5)
    even_value(8)
except MyError as e:
    value = e.get_value()
    print(str(value) + " is not even")
    
# 5 is not even

숫자를 입력받아서 짝수가 아닌 경우에는 MyError를 던지는 함수를 구현해봤다.

MyError는 Exception을 상속해서 구현했다. MyError 객체를 생성할 때 숫자를 하나 넘겨 줄 수 있는데, MyError 객체에 get_value() 메소드를 통해 이 숫자를 얻어올 수 있다. 

even_value() 함수는 숫자를 인자로 받아 홀 수인 경우 MyError를 발생시킨다. 이 때, 짝수가 아닌 숫자를 MyError에 담아서 에러를 발생시킨다. 

try - except 구문에서 MyError가 발생한 경우 발생한 에러 객체에서 숫자 값을 얻어온 다음 print() 함수로 화면에 출력했다.

이런식으로 사용자가 에러를 정의할 수 있고, 에러에 대한 예외처리에 필요한 정보들을 에러 객체에 담아서 전달할 수 있다.


 

파이썬 스크립트 작성에 도움되는 글 모음

파이썬으로 프로그램을 작성할 때 도움되는 글들을 모아본다. 개발환경 [Python] macOS에 파이참 설치 [Python] 파이참 깃허브 연동 [Python] 파이썬 PIP란? [Python] VSCode를 이용한 개발환경 [Python] python3를

hbase.tistory.com

 

댓글