-
Notifications
You must be signed in to change notification settings - Fork 0
2.14. 내용정리: 14일차
파이썬에서 예외(Exception)가 발생하는 경우는 대표적으로 두 가지 경우로 나뉜다.
- 외부적인 문제
- 프로그램 외적인 부분에서 문제가 발생하는 경우
- 코드에 오타를 입력해서 구문 오류가 발생하는 경우
- 파일 입출력을 하는 도중 문제가 발생하는 경우
- 프로그램 외적인 부분에서 문제가 발생하는 경우
- 내부적인 문제
- 프로그램 내부에서 논리적으로 문제가 발생하는 경우
- 즉, 프로그램 상에서 어떤 처리 과정 중 오류가 발생하는 경우
- 알고리즘상의 논리적인 문제점이 발생하는 경우
- 프로그램 내부에서 논리적으로 문제가 발생하는 경우
그리고 이를 구조적인 예외로 범주를 정해 나눠보면 다음과 같다.
- 하드웨어 예외 (Hardware Exception)
- 주로 외부적인 문제에 해당하는 경우이다.
- 하드웨어에서 인식하고 알려주는 예외를 의미한다.
- 즉, 예외상황이 발생했음을 알리는 주체가 하드웨어라는 의미이다.
- 소프트웨어 예외 (Software Exception)
- 주로 내부적인 문제에 해당하는 경우이다.
- 소프트웨어는 우리가 작성한 프로그램과 운영체제(OS)까지 포함된다.
- 즉, 소프트웨어 예외라는 것은 프로그래머가 직접 정의할 수 있는 예외이다.
정리하자면, 하드웨어 예외는 이미 결정된 사안이지만, 소프트웨어 예외는 직접 결정해야 한다는 차이점이 있다.
- 프로그램 실행 시 발생하는 문제점 대부분은 예외이다.
- 처리 불가능한 문제가 발생하면 프로그램은 종료하게 되는데, 이러한 문제를 오류라고 한다.
- 따라서 외부적인 요소에 의한 문제점이든 내부적인 요소에 의한 문제점이든 구조적 예외처리 기법의 관점에서 보면 대부분 해결이 가능해야 한다.
- 즉, 프로그램 실행 시 예측 가능한 대부분의 문제점을 예외로 간주한다.
정리하자면, "오류"는 프로그램 코드를 통해 자체적으로 처리 할 수없는 심각한 조건이고, "예외"는 프로그램의 코드를 통해 처리 할 수있는 예외적인 상황이다.
아래의 예시는 사용자로부터 두 수를 입력 받아서 나눗셈 연산을 하는 코드이다.
x = input('첫번째 수를 입력하세요:')
y = input('두번째 수를 입력하세요:')
z = x / y
print(f'{x} / {y} = {z}')
그럼 아래와 같은 에러 메시지가 출력된다.
TypeError: unsupported operand type(s) for /: 'str' and 'str'
-
input
함수는 사용자로부터 입력받은 값을str
타입으로 반환해주는 함수이다. - 위의 코드를 보면 문자열과 문자열끼리 나눗셈 연산을 한다.
- 문자열과 문자열끼리의 나눗셈을 연산하는 것은 논리적으로 불가능하다.
그래서 아래와 같이 코드를 수정했다.
x = input('첫번째 수를 입력하세요:')
y = input('두번째 수를 입력하세요:')
z = int(x) / int(y)
print(f'{x} / {y} = {z}')
그런데 여기서 분모값인 y
가 0
이 된다면 다음과 같은 에러 메시지가 출력된다.
첫번째 수를 입력하세요:1
두번째 수를 입력하세요:0
Traceback (most recent call last):
File "C:/Users/iamjjintta/test.py", line 3, in <module>
z = int(x) / int(y)
ZeroDivisionError: division by zero
- 어떠한 수를
0
으로 나누는 것은 논리적으로 불가능하다. - 따라서 위의 코드를 실행해보면
ZeroDivisionError
라는 에러 메시지가 출력된다.
이번에는 0
을 나눌 수 없도록 다음과 같이 수정했다.
x = input('첫번째 수를 입력하세요:')
x = int(x)
y = input('두번째 수를 입력하세요:')
y = int(y)
# 'y'가 '0'이면 '1'을 대입시킨다.
if y == 0:
y = 1
z = x / y
print(f'{x} / {y} = {z}')
- 위의 코드는 무려 두번에 걸쳐 코드를 수정했기에, 분명 문제가 없기 때문에 잘 동작해야 한다.
- 그러나, 사용자가 실수로 숫자가 아니라 이상한 문자를 입력한다면 다음과 같은 에러가 발생한다.
첫번째 수를 입력하세요:a
두번째 수를 입력하세요:b
Traceback (most recent call last):
File "C:/Users/iamjjintta/test.py", line 3, in <module>
z = int(x) / int(y)
ValueError: invalid literal for int() with base 10: 'a'
- 즉, 프로그래머가 의도치 않은 결과가 발생하는, 이른바 버그(bug) 가 발생하는 것을 알 수 있다.
- 또한, 수많은 예외적인 상황들을 전부
if-else
문으로 해결할 수가 없다. - 왜냐하면 프로그래머가 모든 예외적인 상황들을 예측 하기에는 분명 한계가 있기 때문이다.
- 뿐만 아니라,
if-else
문으로 모든 예외 상황을 처리하려고 하면 그만큼 코드의 양도 방대해진다.
-
try-except
구문은 이러한 문제점을 해결해주는 문법적인 요소이다. - 사용하는 방법은 다음과 같다.
try:
...
except [발생 오류[as 오류 메시지 변수]]:
...
아래의 예시는 위의 예제를 if-else
문 대신 try-except
문으로 대체한 예시이다.
x = input('첫번째 수를 입력하세요:')
y = input('두번째 수를 입력하세요:')
try:
x = int(x)
y = int(y)
z = x / y
except ZeroDivisionError as e:
# 에러 메시지를 출력해서 확인할 수 있다.
print(e)
y = 1
except ValueError as e2:
print(e2)
print('정수를 입력해야 합니다.')
x = 1
y = 1
print(f'{x} / {y} = {z}')
-
try-except
구문의 마지막에finally
절을 추가할 수 있다. -
finally
절은try
문 수행 도중 예외 발생 여부에 상관없이 항상 수행된다. - 주로
finally
절은 사용한 리소스를close
해야 하는 상황에서 많이 사용한다.- ex. 파일 입출력
x = input('첫번째 수를 입력하세요:')
y = input('두번째 수를 입력하세요:')
try:
x = int(x)
y = int(y)
z = x / y
except ZeroDivisionError as e:
# 에러 메시지를 출력해서 확인할 수 있다.
print(e)
y = 1
except ValueError as e2:
print(e2)
print('정수를 입력해야 합니다.')
x = 1
y = 1
finally:
print(f'{x} / {y} = {z}')
오류를 명시하지 않고 단순히 try-except
구문만 사용하는 경우, 오류의 종류와는 상관없이 오류가 발생하면 except
블록을 수행한다.
x = input('첫번째 수를 입력하세요:')
y = input('두번째 수를 입력하세요:')
try:
x = int(x)
y = int(y)
z = x / y
except:
z = 0
finally:
print(f'{x} / {y} = {z}')
혹은 아래처럼 Exception
이라고 명시하는 경우, 위와 같은 결과를 낼 수 있다.
x = input('첫번째 수를 입력하세요:')
y = input('두번째 수를 입력하세요:')
try:
x = int(x)
y = int(y)
z = x / y
except Exception as e:
print(e)
z = 0
finally:
print(f'{x} / {y} = {z}')
아래처럼 pass
키워드를 사용해 단순히 오류를 회피하기 위한 목적으로도 사용할 수 있다.
x = input('첫번째 수를 입력하세요:')
y = input('두번째 수를 입력하세요:')
try:
x = int(x)
y = int(y)
z = x / y
except:
pass
- 파일 입출력을 하는 경우, 프로그램에서 처리해야 하는 파일의 위치가 해당 디렉터리에 존재하지 않거나 용량이 꽉차서 파일을 생성할 수 없는 경우가 대표적이다.
- 이런 외부적인 요인으로 예외가 발생하는 경우에
try-except
구문을 통해 해결할 수 있다.
file = None
try:
file = open('test.txt')
lines = file.readlines()
for line in lines:
print(line)
except FileNotFoundError as e:
print(e)
print('해당 파일이 존재하지 않습니다.')
finally:
# 예외와는 상관없이 필수적으로 해줘야 하는 작업을 'finally' 절에서 해준다.
if file:
file.close()
-
assert
키워드는 뒤의 조건이True
가 아니면AssertError
를 발생시키는 키워드이다. - 사용하는 방법은
assert <조건식>, <에러 메시지(생략하면 아무런 메시지도 출력되지 않는다.)>
와 같이 사용한다.
아래의 코드를 실행하면 다음과 같은 결과를 확인할 수 있다.
a = 3
assert a == 2
결과
Traceback (most recent call last):
File "C:/Users/iamjjintta/test.py", line 2, in <module>
assert a == 2
AssertionError
예외가 발생할 경우, 예외 메시지를 출력하도록 할 수 있다.
a = 3
assert a == 2, "'a'의 값이 2가 아닙니다."
결과
Traceback (most recent call last):
File "C:/Users/iamjjintta/test.py", line 2, in <module>
assert a == 2, "'a'의 값이 2가 아닙니다."
AssertionError: 'a'의 값이 2가 아닙니다.
-
assert
키워드는 개발자가 프로그램을 만드는 과정에 관여한다. - 원하는 조건의 변수 값을 보증받을 때까지
assert
키워드로 테스트 할 수 있다.- 즉, 코드를 테스트 하는 용도로 많이 사용된다.
- 이는 단순히 에러를 찾는것이 아니라 값을 보증하기 위해 사용된다.
- 예를 들어, 어떤 함수는 성능을 높이기 위해 반드시 정수만을 입력받아 처리하도록 만들 수 있다고 하자.
- 이런 함수를 만들기 위해서는 반드시 함수에 정수만 들어오는지 확인할 필요가 있다.
- 이를 위해
if
문을 사용할 수도 있고 앞서 살펴본try-except
구문을 사용할 수도 있지만,assert
키워드로 가정 설정문을 사용하는 방법도 있다. - 이처럼 실수를 가정해 값을 보증하는 방식으로 코딩 하기 때문에 이를 방어적 프로그래밍이라 부른다.
-
raise
키워드는assert
키워드와는 달리, 무조건 예외를 발생시키는 키워드이다. - 사용하는 방법은
raise <예외 종류(생략 가능)>
와 같이 사용한다.
아래의 코드를 실행하면 무조건 예외가 발생한다.
def f(x):
raise
f(10)
결과
Traceback (most recent call last):
File "C:/Users/iamjjintta/test.py", line 4, in <module>
f(10)
File "C:/Users/iamjjintta/test.py", line 2, in f
raise
RuntimeError: No active exception to reraise
-
assert
키워드와 마찬가지로 예외가 발생할 때 예외의 종류를 직접 정할 수 있다. - 단, 발생시킬 예외는
Exception
객체를 상속받은 예외여야만 한다.
def f(x):
raise ValueError
f(10)
결과
Traceback (most recent call last):
File "C:/Users/iamjjintta/test.py", line 4, in <module>
f(10)
File "C:/Users/iamjjintta/test.py", line 2, in f
raise ValueError
ValueError
assert
키워드처럼 메시지가 같이 출력되도록 하고 싶은 경우에는 다음과 같이 작성한다.
def f(x):
raise Exception('예외 발생')
f(10)
결과
Traceback (most recent call last):
File "C:/Users/iamjjintta/test.py", line 4, in <module>
f(10)
File "C:/Users/iamjjintta/test.py", line 2, in f
raise Exception('예외 발생')
Exception: 예외 발생
- 앞서
raise
키워드에서 살펴보았듯, 예외를 발생시키는 경우, 예외의 종류를 지정할 수 있다고 서술했다. - 기존에 정의된 예외뿐만 아니라 자신이 직접 예외 객체를 만들어서 정의할 수 있다.
- 예외 객체를 만들기 위해서는
BaseException
이라는 클래스를 상속받은 객체여야만 한다.
# 'BaseException' 클래스를 상속받은 예외 클래스 'MyException' 정의
class MyException(BaseException):
pass
def f(x):
# 정의한 예외 발생시키기
raise MyException('예외 발생')
f(10)
결과
Traceback (most recent call last):
File "C:/Users/iamjjintta/test.py", line 7, in <module>
f(10)
File "C:/Users/iamjjintta/test.py", line 5, in f
raise MyException('예외 발생')
MyException: 예외 발생