🖇 무엇이 에러인가
🖇 예외 처리
🖇 에러 발생시키기
🖇 자주 발생하는 에러 종류
예외 처리는 중요하다.
현실의 데이터는 언제나 깨끗하고 완벽하지 않다. 누락된 값, 잘못된 형식, 존재하지 않는 파일, 서버 응답 오류, 나눗셈의 0, 타입 불일치 등 예상치 못한 상황은 언제든 발생할 수 있다.
꼭 파이썬에서만이 아니더라도 시스템이 의도치 않게 동작하는 상황에 대처하기 위해서 예외 처리는 핵심적으로 사용된다.
실제로 경험했던 상황을 이야기 해보자면,
JavaScript로 서비스를 구현하면서 예외 처리를 제대로 하지 않은 로직이 있었다.
서버가 터져서 응답이 들어오지 않았을 때 사용자로부터 UI가 깨진다는 피드백을 받은 적이 있다.
이렇듯 예외 처리 기능은 이렇게 단순한 에러 방지뿐만 아니라, 안정적인 프로그램을 만드는 데 있어 핵심적인 역할을 하게 된다.
이 글에서는
파이썬에서 예외 처리를 해야하는 에러 상황이 무엇인지 정의하고, 올바르게 예외 처리하는 방법을 정리했다.
파이썬을 사용하면서 주요 예외들을 익혀두면 에러 메시지를 빠르게 이해하고 적절히 처리할 수 있을 것이다.
[파이썬 공식 문서] 에러와 예외
'에러' 와 '오류' 가 자주 혼용되지만, 공식 문서에 따라 '에러' 로 표기하겠다.
에러란?
: 프로그램을 실행하는 도중 발생하는 문제 상황
: 에러는 프로그램을 강제로 종료시키며, 발생 원인에 따라 두 가지로 나눌 수 있다.
프로그래밍 실행 전에 발생하는 에러 → 문법 에러
프로그래밍 실행 후에 발생하는 에러 → 예외 또는 런타임 에러
마찬가지로 '문법 에러' 와 '구문 에러' 도 자주 혼용되지만, 공식 문에서 따라 '문법 에러' 로 표기하겠다.
문법 에러란?
파이썬 문법에 맞지 않아서 프로그래밍 실행도 전에 오류가 발생하는 것으로,
컴파일 단계에서 발생한다.
프로그래밍이 실행 전에 에러가 나는 것이기 때문에
⇒ 따로 예외 처리를 하는 것이 아니라,
⇒ 오류가 발생하는 코드를 알맞은 코드로 수정해주는 방법으로 처리
예외 또는 런타임 에러란?
실행 중에 발생하는 에러로, 문법은 맞지만 동작 중 에러가 발생하는 것이다.
문법에 문제가 없어도 코드를 실행할 때(런타임) 프로세스에서 발생할 수 있는 예외들을 말한다. 문법 에러를 제외한 나머지 에러 상황을 생각하면 된다.
⇒ 이는 예외 처리를 통해 해결한다
예외 처리(Exceptional Handling)
: 예외(에러 상황)를 해결하는 모든 것
: 예외 발생 시 프로그램이 종료되지 않고 처리될 수 있도록 한다
예외 처리 방법에는 크게 두 가지가 있다.
조건문을 활용해서 예외 상황을 처리한다
장점: try 구문을 사용했을 때보다 속도가 약간 빠르다
단점: 예외가 발생할 수 있는 모든 상황을 예측하고, 모두 조건문으로 처리해야 한다
# 예제. 사용자가 정수를 입력하면 그 정수를 출력하기
num = input('정수를 입력하세요 : ')
# 입력값이 0과 양의 정수일 때
if num.isdigit():
print(f'입력한 정수는 {num}입니다.')
# 입력값이 음의 정수일 때
# isdigit() 함수는 숫자만 판별하므로 음수를 표시하는 -는 걸러내지 못한다
elif num.lstrip('-').isdigit():
print(f'입력한 정수는 {num}입니다.')
# 입력값이 정수가 아닌 경우 예외 처리
else:
print('정수가 아닙니다.')
예제에서 알 수 있듯이,
조건문을 사용한 예외 처리를 하게 되면 정수가 아닌 입력값을 하나하나 가정하고 모든 상황을 고려한 예외 처리를 해야 한다.
try - except 구문을 활용하면 어떤 상황에서 예외가 발생하는지 완벽하게 이해하지 않아도 프로그램이 강제로 죽어 버리는 상황을 방지할 수 있다.try:
예외가 발생할 수 있는 코드
except (발생 오류 (as 발생 오류 변수)):
예외가 발생했을 때 실행할 코드except 구문에 아무 것도 넣지 않고 try 구문 사용
구문 오류를 방지하기 위해 except 구문에 pass 키워드를 넣어 준다
오류 발생 시에도, pass 키워드 이후의 코드는 동작한다
try:
예외가 발생할 수 있는 코드
except:
pass
else를 추가하면 예외가 발생하지 않았을 때 실행할 코드를 지정할 수 있다
예외 발생 가능성이 있는 코드만 try 구문 내부에 넣고,
나머지는 모두 else 구문으로 빼는 경우가 많다
try:
예외가 발생할 수 있는 코드
except:
에외가 발생했을 때 실행할 코드
else:
예외가 발생하지 않았을 때 실행할 코드
🧐 Q. 에러가 발생하면 어차피 try 안의 코드가 중단될 텐데, 굳이 else는 왜 쓰는 걸까?
예외가 날 수 있는 부분과 그렇지 않은 부분을 구분해서 코드 가독성과 명확성을 높이기 위해 사용한다.
| 블록 | 역할 |
|---|---|
try | 예외가 발생할 수 있는 (위험한) 코드를 넣는 곳 |
except | 예외가 발생했을 때 실행할 코드 |
else | 예외가 발생하지 않았을 때만 실행할 (안전한) 코드 |
finally 구문은 예외가 발생하든 발생하지 않든 무조건 실행하고 싶은 코드가 있을 경우 사용한다try:
예외가 발생할 수 있는 코드
except:
예외가 발생했을 때 실행할 코드
else:
예외가 발생하지 않았을 때 실행할 코드
finally:
에외와 관계없이 무조건 실행되는 코드예외 처리 구문들은 아래의 규칙을 따른다.
try 구문은 단독으로 쓰일 수 없고 except 또는 finally 구문과 함께 쓰여야 한다.else 구문은 except와 함께 쓰이고, except 구문 뒤에 사용해야 한다.예외 처리 구문 규칙에 의해,
아래 다섯 가지 조합 외의 구문은 오류가 발생한다.
try+except
try+except+else
try+except+finally
try+except+else+finally
try+finally
프로그램에 일부러 예외를 발생하고 싶을 때 사용한다.
raise에 예외를 지정하고 에러 메시지를 넣는다
try:
x = int(input('3의 배수를 입력하세요: '))
if x % 3 != 0: # x가 3의 배수가 아니면
raise Exception('3의 배수가 아닙니다.') # 예외를 발생시킨다
print(x)
print('예외가 발생했습니다.', e)
3의 배수를 입력하세요: 5 (입력)
예외가 발생했습니다. 3의 배수가 아닙니다.
예제를 살펴보면
5는 3의 배수가 아니므로 raise Exception('3의 배수가 아닙니다.')로 예외를 발생시킨다.
이때 Exception에 넣은 에러 메시지는 except Exception as e:의 e에 들어간다.
raise로 예외를 발생시키면 raise 아래에 있는 코드는 실행되지 않고 바로 except로 넘어간다.
[참고] 파이썬 코딩 도장
예외는 클래스 상속으로 구현되며 아래와 같은 계층으로 이루어져 있다.
일반적으로 파이썬에서 새로운 예외를 만들 때는 Exception을 상속받아서 구현한다.
전체 계층도는 [파이썬 공식 문서] Exception hierarchy 참고
# 예외 계층 구조
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── ArithmeticError
│ ├── ZeroDivisionError
│ └── OverflowError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── SyntaxError
│ ├── IndentationError
├── TypeError
├── ValueError
├── NameError
├── FileNotFoundError
├── ImportError
├── AttributeError
├── AssertionError
└── ...
SyntaxError
NameError
print(a)
NameError: name 'a' is not defined
ZeroDivisionError
print(10 / 0)
ZeroDivisionError: division by zero
IndexError
a = [10, 20, 30]
print(a[4])
IndexError: list index out of range
ValueError : 참조 값이 없음
a = [10, 20, 30]
a.remove(40)
ValueError: list.remove(x): x not in list
KeyError
get() 메소드를 활용하여 해결할 수 있다mydict.get('키') -> 키가 없는 경우 None 반환mydict = {'Kim': 1, 'Lee': 2}
print(mydict['Park'])
KeyError: 'Park'
AttributeError
import time
time.month()
AttributeError: module 'time' has no attribute 'month'
FileNotFoundError
f = open('ex.txt', 'r')
FileNotFoundError: [Errno 2] No such file or directory: 'ex.txt'
TypeError
a = [1, 2]
b = "Hello"
print(a + b)
TypeError: can only concatenate list (not "str") to list
EAFP
“It’s Easier to Ask Forgiveness than Permission”
try)시키고, 만약 에러가 발생하면 그때 처리(except)하는 전략LBYL
“Look Before You Leap”
언제나 그렇듯 완벽한 정답은 없지만
주어진 상황에서 두 전략을 적절히 고려하여 예외 상황을 효과적으로 처리할 수 있게 되길 바란다.