예외처리(Exception Handling)는 프로그램이 에러로 중단되지 않도록 안전하게 처리하는 기능이다.
예외란 프로그램의 실행 중에 에러가 발생하고 에러가 발생하면 프로그램이 멈추고 에러 메세지를 출력하는데 이런 상황을 예외가 발생했다고 한다.
예외 처리를 해주는 이유는 프로그램이란것은 어떤 문제들이 발생하더라도 서비스가 중단되면 안되기 때문에 오류가 발생하더라도 종료되지 않고 확인하여 추후 오류가 발생한 부분을 수정 할 수 있게 하기 위함이다.
이 예외처리를 할때 사용하는 구문이 try ~ except 구문이다.
try ~ except
try ~ except의 기본 구조는 아래와 같다.
try:
# 에러가 발생할 가능성이 있는 코드
except 에러종류:
# 에러가 났을 때 실행할 코드
프로그램을 만들다보면 여러가지 에러를 만나게 되는데 대표적으로 ZeroDivisionError, ValueError 등의 에러이다.
try의 아래에 있는 실행 코드 내부에서 exception이 발생하는 경우 보통은 아래와 같이 에러 구문이 출력되는데
보면
코드가 이렇게 작성되어 있더라도
Exception이 발생한 시점 이후로 코드가 실행되지 않고 종료되는 것을 볼 수 있다.
이때 try ~ exception으로 감싸주면
정상적으로 나중에 있는 코드도 실행되는 것을 볼 수 있다.
그런데 이제 except구문 내부에 우리가 발생 할것으로 예상되는 ZeroDivisionError를 넣어주면
ZeroDivisionError가 발생한다면 except 내부의 코드를 실행시킨다.
그러면 다른 종류의 Error가 발생하면 어떨까?
이번엔 내부에 리스트를 만들고 리스트의 index를 넘기는 값을 출력하도록 코드를 작성해봤다.
실행해보면
아까랑 똑같이 그냥 프로그램이 종료돼 버린다.
그렇기에 두가지 에러가 발생할것으로 예상된다면 모두 적어줘야 한다.
이때 그냥 except구문을 아래로 추가해서 작성하면 된다.
Exception as e
에러가 발생했을때 무슨 에러가 발생했는지를 상세하게 확인해볼 수 있는 구문이다
지금 위에서 만든 구문 내부에서 우리가 작성한 ~Error Occured라는 구분은 우리가 작성한 부분이고
이렇게 추가해주면
이렇게 시스템 상에서 발생한 에러에 대한 내용을 출력해줘 좀 더 구체적인 정보을 확인할 수 있다.
모든 예외를 한번에 잡기
이렇게 우리가 모든 예외를 지정해서 처리를 해준다면 코드가 길어지기도 길어지고 놓치는 부분이 있어 문제가 발생할 여지가 있다.
이럴때 그냥 어떤 에러인지 작성하지 않고 Exception으로 지정해주면서 as e를 사용하면 어떤 에러가 발생했는지를 확인할 수 있을 뿐만 아니라 모든 에러에 대해서 Exception 처리를 해줄 수 있어 편리하다.
else
try ~ except 구분 이후에 else라는 구문을 추가해줄 수 있는데 이는 try 내부에서 exception이 발생하지 않은 경우에 실행된다.
finally
finally 구문은 else처럼 마지막 구문에 붙여 추가하는 구문으로 esle랑은 다르게 에러가 있건 없건 무조건 실행되는 구문이다.
이게 필요한 이유는 DB를 연결하거나 파일을 쓰기 위해서 스트림을 열어둔 경우에 자원을 해제하는 경우때문이다.
이건 정상 기능을 할때고 exception의 상황인 경우도
finally 구문은 무조건 실행되는 것을 볼 수 있다.
raise
raise는 사용자가 의도적으로 예외를 발생시키기 위해서 사용하는 키워드이다.
예외를 발생시키는 raise가 필요한 이유는 사용자가 직접 잘못된 입력을 체크하거나, 코드의 흐름을 멈추고 싶거나, 사용자가 원하는 에러의 종류를 발생시키고 싶은 등의 필요성이 존재하기 때문이다.
예시를 들어보자.
사용자가 나이를 입력받고 싶을때 이용자가 음수를 입력하는 경우 로직 내부에서 문제가 발생하지 않을 가능성이 있다.
이럴때는 아예 예외 자체가 나오진않으나 만든 사람의 의도를 벗어난 행동이다.
그렇기에 이런 행동을 방지할때 아래와 같이 사용하면
age = -1
if age < 0:
raise ValueError("나이는 음수가 될 수 없습니다.")
print(f"나이: {age}")
Exception으로 오류를 발생시켜 잘못된 입력으로 인식시킬 수 있게 된다.
그런데 보통 raise는 try ~ except 내부에서 처리하게 된다.
try:
age = -1
if age < 0:
raise ValueError("나이는 음수가 될 수 없습니다.")
except ValueError as e:
print(e)
print(f"나이: {age}")
여기서는 ValueError를 사용했으나 이 Error의 종류를 클래스로 사용자가 만드는것도 가능하다.
class UserError(Exception):
pass
try:
age = -1
if age < 0:
raise UserError("사용자의 부적절한 입력입니다")
except UserError as e:
print(e)
print(f"나이: {age}")
raise가 except 내부에서 사용될때는 기존에 발생한 에러를 예외처리한 상태말고 그냥 에러로 다시 던질 수 도 있다.
보면 에러 처리로 division by zero가 나왔음에도 불구하고 raise를 만나면서 다시 에러를 출력한다
이때는 raise 뒤에 아무런 내용을 적지 않아도 된다.
해당 내용은 조금 심화적인 내용으로 보인다.
예를 들면 상위 함수에서 하위 함수의 익셉션을 처리하고 하위 함수의 익셉션은 그냥 로그를 남기는 용도로 작성한다면
def except_func():
try:
print(3 / 0)
except Exception as e:
print("로그용 try ~ exception: ", e)
raise
def fun():
try:
except_func()
except Exception as e :
print("상위에서 예외 처리:", e)
fun()
이렇게 사용하기 위함이라고 한다.
원래 Exception이란건 어느정도 심각하지 않은 에러의 발생에서 처리하는 것으로 보이는데 이게 상위에서 큰 문제를 발생시킬 수 있는 여지가 있다면 위까지 이 에러를 알려야만 하고 이때 이 raise를 사용하는 것으로 보인다.
### raise의 활용에 대해서는 조금 공부를 많이 해야할 듯 싶다.