ㅅㅇ
python 프로그래밍 : 예외와 예외처리 본문
1. 예외 처리개요
예외의 종류에는?
1) System Exception
- 파이썬 문법이나 어문 규칙을 어겨서 때문에 발생하는 오류
- 예외 처리를 해결할 수 있긴 하지만 대부분 경우 코드를 수정한다.
- 다른 언어에서는 컴파일. 파이썬은 실행에서.
2) Application Exception
- 프로그램이 정한 업무규칙을 어기는 상황에서 발생하는 오류
- 상황이 발생하면 명시적으로 예외를 발생시킨 뒤 예외처리를 통해 처리한다.
- ex.) 다섯글자의 값을 입력받아야 한다는 규칙에서 여섯 글자의 값이 입력들어온다.
문법 오류 없지만, 내 작업 환경 규칙에서 오류
파이썬 실행 시 발생되는 주요 예외
- 파이썬 표준 라이브러리에 정의된 주요 예외 (Exception에 상속)
- 예외 이름은 보통 Error로 끝난다.
- 이를 알아야 하는 이유 : 어디에서 어떻게 고칠지 파악할 수 있어야 한다.
SyntaxError : 파이썬 문법에 어긋난 코드 작성 시 발생
NameError : 정의되지 않은 변수나 함수를 호출한 경우 발생
TypeError : 잘못된 타입의 값을 전달할 경우 발생
ValueError : 타입은 맞는데 값이 잘못된 경우 발생
IndexError : 없는 index로 리스트나 튜플 값 조회 시 발생
KeyError : 없는 key로 딕셔너리의 값 조회 시 발생
2. 예외 처리하기
(1) 예외처리
예외처리하기란?
- 발생한 예외(Exception)을 처리해서 프로그램을 정상화시킨다.
- try과 except, else, finally가 있다.
- try 구문
- '예외가 발생할 가능성이 있는 코드'와 '발생가능성 있는 코드와 연결된 코드'가 들어간다는 것을 기억하자.
예외처리하기 구문 _ try, except 구문
try:
# try block
예외가 발생할 가능성이 있는 코드
발생가능성 있는 코드와 연결된 코드
except:
Handling 코드
try block에서 예외가 발생했을 때 그것을 처리하는 코드들
# EX. 예외 발생 예제와 예외처리 (1)
# EX. 예외 발생
# 2줄 ValueError 오류
# => int 정수 변환 함수 괄호 안에는 정수가 들어와야 하는데 문자열이 들어왔다.
# 발생할 지, 안 할지 모르지만 가능성이 있다.
# => 2줄 오류로 3줄부터 실행할 수 없다.
# => 실행할 수 없는 코드 중 실행 처리를 원하는 코드를 예외처리해주자.
num_str = input("정수:")
num = int(num_str) # 예외발생 가능성 있는 코드
result = num ** 3 # 예외가 발생하면 실행할 수 없는 코드
print("결과:", result) # 예외가 발생하면 실행할 수 없는 코드
print("종료")
# EX. 예외처리
# 정수 입력했다면, num = int(num_str) 정상 실행. 그대로 아래 줄 실행하고(except 실행 안되고) 종료.
# 문자열 입력했다면, num = int(num_str) 오류. 아래 줄 실행 안되고 except 구문으로 가 실행하고 종료.
# => 둘 다 정상화되고(시키고) 종료.
num_str = input("정수:")
try:
num = int(num_str) # 예외발생 가능성 있는 코드
result = num ** 3 # 예외가 발생하면 실행할 수 없는 코드
print("결과:", result) # 예외가 발생하면 실행할 수 없는 코드
except:
print(f"{num_str}은 정수로 바꿀 수 없습니다. 다시 실행해서 정수를 입력하세요.")
print("종료")
# EX. 예외 발생 예제와 예외처리 (2)
# 예외처리 예제2
# 예외발생 가능성 있는 코드1, 코드2 : ValueError
# 예외발생 가능성있는 코드3 (num2 = 0 들어가면) : ZeroDivisionError
# =>코드1, 코드2 오류 이유가 다르다는 것은 상황 자체가 다른 것.
# -> 이것 다르게 처리할 수 있는데 다음 예제
num_str1 = input("정수1:")
num_str2 = input("정수2:")
########이 코드들은 예외발생으로 연결된(연관성) 코드 -> try로 묶는######
try:
num1 = int(num_str1) # 예외발생 가능성있는 코드1
num2 = int(num_str2) # 예외발생 가능성있는 코드2 이자 예외1와 연결된 코드
result = num1 / num2 # 예외발생 가능성있는 코드3
print("나눈 결과:", result) # 예외와 연결된 코드
################################################################
except:
print("예외발생")
print("프로그램을 종료합니다.")
(2) 예외처리 : 특정 예외만 처리하기
- 발생할 수 있는 Exception이 여러 종류이고 Exception 별로 다르게 처리할 경우하고 싶을 때
- 특정 예외만 처리하자.
- except 예외이름:
- except 구문도 순차적으로 첫번째 except 오류 아니면 두번째 except 확인...
-> default except: 작성 시 가장 마자막에 해야 하는 이유.
- as 변수
: 예외 클래스에서 만들어진 객체의 변수
- 모든 예외는 클래스이다.
- 이를 활용하면 오류 났을 때 출력 오류 메시지를 사용할 수 잇다.
- 구문 예시
except ValueError as ve:
print("정수를 입력하세요.", ve)
# 출력 메시지 :
# 정수를 입력하세요. invalid literal for int() with base 10: '1-'
# EX. 예외 발생 예제와 예외처리 (3) _ 특정 예외만 처리, as변수 이용, default except 넣기
# 오류 이유가 다르다는 것은 상황 자체가 다른 것.
# 이 상황을 다르게 처리하고 싶다면? => 특정 예외만 처리하라.
num_str1 = input("정수1:")
num_str2 = input("정수2:")
try:
num1 = int(num_str1)
num2 = int(num_str2)
result = num1 / num2
print("나눈 결과:", result)
# Value Error만 처리. 다른 오류 발생하면 예외처리 안 했으니 당연히 오류.
except ValueError as ve:
print("정수를 입력하세요.", ve)
# ZeroDivisionError만 처리.
except ZeroDivisionError as ze:
print("두번째 숫자는 0 이외의 정수를 입력하세요.", ze)
# default 처리(순차적으로 하기에 가장 마지막에 처리해야 함.)
except:
print("ValueError, ZeroDivisionError 이외의 예외가 발생할 경우 처리.")
print("프로그램을 종료합니다.")
___ 예외처리는 오류 발생 어느 부분이든 다 구현가능하다. 프로그램에 따라 구현.
___(1) 설명
<출력 오류 메시지>
- 출력 메시지를 보면 Exception 오류가 발생한 곳, 발생 이유를 알려준다.
- 예외가 시작되는 부분의 시초는 가장 아래 메시지 부분이다. 그 위 오류 줄은 호출 경로이다.
( 이 두번째 줄을 실행 시켰는데, 이 줄에서 말하는 함수를 가보니, 이 클래스로 가게 되었는데 이 클래스의 어떤 줄에서 오류가 발생했다. 제일 아래 오류 코드에서 말하는 것은 두 번째 실행 코드 줄을 보여주고, 그 위 오류 줄들은 함수, 클래스 .. 그 경로를 말해준다.)
- 코드 수정할 때 이 경로를, 이 흐름을 이해해야 한다.
여기서 '실행의 흐름' 이해가 중요한데,
늘 호출된 곳의 종류 후(정상 종료든 비정상 종료든)
'호출한 곳으로 따라 가야' 한다.
코드 13 줄 실행하니 f1 함수로 갔다.
f1 함수에는 f2 함수 호출 줄이 있어 f2 함수로 갔다.
f2 함수에는 f3 함수 호출 줄이 있어 f3를 갔다.
정상적으로 수행했다면,
f3 코드 모두 실행 완료 했으면, f2 해당 줄로 돌아가.
f2 줄 (f3 함수 호출한 줄이 실행 완료) 그 다음 줄부터 순차적으로 또 실행.
f2 코드 모두 실행 완료 했으면, 다시 f1 해당 줄로 돌아가.
f1 줄 (f2를 호출한 줄. 이게 실행 완료) 그 다음 줄부터 순차적으로 또 실행.
f1 코드 모두 실행 완료 했으면, 다시 실행 코드 13줄로 돌아가.
코드 13 줄 (f1 함수 호출한 줄이 실행 완료) 그 다음 줄부터 순차적으로 또 실행.
다 끝났으면 종료.
==> return : 정상 종료 후 돌아가는 것.
Exception 발생. 비정상적으로 수행했다면,
f3 코드에서 오류, f2 해당 줄로 돌아가.
f2 줄 (f3 함수 호출한 줄) 오류. f1 해당 줄로 돌아가.
f1 줄 (f2 함수를 호출한 줄) 오류. 13줄로 돌아가.
13줄 오류.
종료.
==> raise : 비정상 종료 후 돌아가는 것.
- 그냥 끝난 것 같지만(실행되지 않는 부분이 생기니), 출력(오류메시지 또한)이 되었다는 것은 처리! 돌아갔다는 것.
- 그냥 끝났으면 오류메시지 출력 조차 되지 않았을 것.
==>실행 코드 13줄의 f(1) 호출문, f(1) 내 f(2)호출문 줄 , f(2) 내 f(3)호출문, f(3)의 exception발생이유 줄
모두 Exception 발생 부분이다. -> Exception handling 할 수 있는 부분 (프로그램에 따라 어디서 할 지 결정.)
___(2) 처리 예시
# EX. 예외 발생 예제와 예외처리 (4) _ 함수를 호출한 코드에서 예외처리(함수 내에서 말고)
- 호출문 예외처리 경우 설명서를 써준다.
def divide():
"""
설명
[parameter]
[return value]
[exception]
ValueError 발생 이유
ZeroDivisionError 발생이유
"""
num_str1 = input("정수1:")
num_str2 = input("정수2:")
num1 = int(num_str1)
num2 = int(num_str2)
result = num1 / num2
print("나눈 결과:", result)
# return # 원래 함수 마지막은 늘 return. 현 코드 생략 한 것.
# return 의 개념은 정상 종료 후 divide() 줄로 돌아가는 것. 그 다음 줄 실행.
try:
divide() # Exception 발생할 가능성이 있는 코드
except:
print("나누는 작업을 정상적으로 처리하지 못했습니다.")
print("프로그램을 종료합니다.")
- divide() 즉, 호출한 곳 또한, Exception(ValueError)가 발생할 가능성이 있는 코드인 것이다.
=> 호출한 곳에서도 예외처리 가능.
=> why? exception 상항에서 오류발생으로 아래 코드 실행 안 하고 호출문 divide()로 돌아간다.
그래서 파이썬 실행 환경까지 가기에, 본 오류 메시지를 출력해주는 것.
___(3) 호출한 곳에서 처리해야 할 때.? 이유.?
- 이때 오류는 divide2(10,0)에서 0을 넣은 것을 사실 def 함수에서가 아니라 호출한 곳에서 난것.
- 위 def는 나누기만 하는 것이다.
잘못된 값이 들어왔을 때 어떻게 처리할 지는 이 코드에서 진행되야 한다.
예외처리를 어떻게 할지. 넘겨버릴지 아님 plus 처리를 시킨지...
이것은 이걸 불러온 사람, 호출한 사람이 결정.
=> 여기서 예외처리 해줘야 한다.
# EX. 예외 발생 예제와 예외처리 (5) _ 함수를 호출한 코드에서 예외처리(함수 내에서 말고)
def divide2(num1:int, num2:int) -> int:
# num1:int -> 매개변수 데이터 타입를 알려주는 것. 꼭 넣는 지정이 아님.
# () -> int : 리턴 데이터 타입에 대한 힌트
"""설명서
두개의 숫자를 나누는 함수
[parameter]
num1: int - 피연산자
num2: int - 피연산자
[return value]
int - 나눈 결과
[exception]
ValueError
ZeroDivisionError : num2가 0일 경우 발생.
"""
return num1/num2
# 호출한 곳
try:
p1 = divide2(10,10)
p2 = divide2(10,0)
print(p)
except:
a = divide2(10,1)
# a = plus(10,0) # 어떻게 처리할 지는 여기서!!!!
(3) 예외처리 : else
- try 블록에서 예외가 발생하지 않았을 경우 실행할 코드를 작성한다.
- except 다음에 와야 한다.
- try 아래에 쓰면 되기에 잘 안 쓴다.
# EX. 예외 발생 예제와 예외처리 (6) _ else
try:
r = divide2(10, 10) # else쓸 때는 예외 발생 가능성 있는 코드만 넣자!
# print("나눈결과:", r) # 이건 예외가 발생하지 않을 경우 실행할 경우 코드인데, 여기서는 else로
# 사실 그냥 여기에 넣으면 되서 잘 안 씀.
except:
print("예외발생") # 예외가 발생하며 실행할 코드
else:
print("나눈결과:", r) # 예외가 발생하지 않을 경우 실행할 경우를 여기에!
print("종료")
(4) 예외처리 : finally
- finally block
- try에서 예외 발생 및 처리 여부와 상관없이 100% 실행을 보장하는 코드 블럭. (바깥에 넣어도 실행 되지 않을 수 있다.)
- 주로, 외부자원과 연결해서 데이터를 주고받을 때 마지막 연결 끊는(닫는) 를 주로 넣는다.
- 입출력에서 많이 쓴다. (다음 단원에서 예제)
- 많이는 안 쓴다.
- except와 else보다 먼저 올 수 없다.
# 밖에 둬도 100% 실행 안되는 상황.
try:
print(1/0)
print("무조건 실행될 코드") # 100% 실행 가능성 없다.
except ValueError:
print("무조건 실행될 코드") # 100% 실행 가능성 없다.
print("무조건 실행될 코드")
# 만약, Value Error만 예외처리 했을 때, 다른 오류가 발생 시에는 밖에 두어도 실행 안됨.
# 이 코드 또한 100% 실행 가능성 없는 것이다. 그래서 finally
# EX. 예외 발생 예제와 예외처리 (5) _ finally
try:
# 외부 자원과 연결
# 외부자원과 데이터를 주고받는 코드
print(1/0)
print("코드") # 100% 실행 가능성 없다.
except ValueError:
# 데이터를 주고받다가 예외를 처리하는 코드
print("코드") # 100% 실행 가능성 없다.
finally:
# 연결 닫는 (끊는) 코드
print("무조건 실행될 코드")
# 현재 ValueError 만을 예외처리하여 오류 메시지 뜨지만,
# 어쨌든 무조건 실행될 코드를 finally: 통해 출력했다.
(5) 같은 일을 하는 메소드(함수)가 예외발생 또는 None반환을 하는 경우 예제
- d["key"] 없는 키 조회하면 keyError
- d.get("key") 없는 키 조회하면 None반환(내부적으로 정상처리를 해준다.)
=> 둘의 예외처리 방식이 다르다.
# d["key"]의 예외처리
try:
d["key"] # keyError를 발생
except:
print("없습니다.")
# v =d.get("key") 예외처리
v = d.get("key") # None반환
if v != None:
rint(v)
else:
print("없습니다.")
print(v)
- 문자열 메소드 find와 index 또한 위 처럼 처리 가능. 해보기.
(6) 예제
EX. 예외 발생 예제와 예외처리 (6) _ 객체 특수메소드 비교연산자 관련 코드
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# > 연산자 재정의
def __gt__(self, obj):
result = False
if isinstance(obj, Person): # obj가 Person 타입일 때
result = self.age > obj.age # 원하는 연산
# exception handling
else: # obj가 Person타입 아닐 때
try: # 숫자데이터 제외 예외 발생 가능성있음.
result = self.age > obj # obj에 정수 30이 들어왔을 때 오류이지만,
except:
pass # 이를 패스하고 return 하라.
return result
p1 = Person("홍길동", 20)
p2 = Person("이순신", 15)
p1 > p2, p1 > 30
# p1 > p2 일 때 정상 종료 후 Ture return
# p1 > 30 일 때 예외처리 패스로 10 줄에 의해 False 출력.
# p1이 self로, p2가 obj로
# p1이 self로, 30이 obj로
3. 사용자 정의 예외 구현 및 예외 발생시키기
1. 예외상속구조
- 예외를 모두 클래스로 정의해 사용한다.
- 예외가 발생하는 상황과 관련된 instance변수, 메소드 정의한 클래스
- ValueError ... 오류는 모두 클래스이다.!
- 예외가 발생한 상황에 따라 클래스가 나눠지는 것.
- except ValueError as 변수 : 클래스에서 만들어진 객체의 변수
- ===> 사용자 정의 예외 객체가 할당된 변수 e에 의해 __str__ 메소드 호출. 블럭에 있는 return 실행.
2. 예외 클래스 작성
- 프로그램 조직 흐름상 예외가 발생해야 하는 경우 예외 클래스를 정의할 수 있다.
- 사용자 정의 예외는 Exception을 상속받아 만든다.
- 왜? 모든 예외는 Exception(클래스)을 상속받아 구현되기 때문에
3. 예외 발생시키기
- rasie 예외객체
- 예외를 발생시킨다.
- 대부분 if문에 작성한다.
- 예외가 발생해야 하는 조건 True일 경우
4.return과 raie의 의미
- return의 의미 : 정상 종료 상태로. 호출한 곳으로 돌아가라.
- rasie의 의미 : 비정상 종료 상태로. 호출한 곳으로 돌아가라.
EX. 사용자정의 예외 구현 및 발생 (1)
_ 클래스 선언부 (상속받아 사용자 정의 예외 클래스 만들기)
class InvalidMonthException(Exception): # Exception 상속
def __init__(self, invalid_month):
self.invalid_month = invalid_month
def __str__(self):
return f"월은 1_ 12 사이의 정수만 가능합니다. 사용된 월은: {self.invalid_month}입니다."
- Exception 상속받아 클래스 선언
- 클래스 블럭에는 __str__ 을 통해
def __str__(self):
return 출력메시지에 띄울 말
as변수를 통해 예외처리 시 출력 메시지에 띄우고 싶은 말을 쓸 수 있다.
(클래스 블럭에는 내가 원하는 대로 작성하면 된다. )
_ 함수부 ( 원하는 동작 아니면 예외 만들어주기 rasie )
def set_month(month):
if month >=1 and month <=12:
print(f"{month}월을 설정했습니다.") # 원하는 정상동작
else:
raise InvalidMonthException(month) # else 일 경우 모두 예외 발생
# raise Exception() #예외발생
# Exception 클래스로 해도 되지만 구체적으로 무슨 에러인지 모름.
# 그래서 클래스 정의를 한다.
# 잘못된 상황인지 어떤 상황인 지 알 수 있어야 한다.해결할 수 있게
# 잘 못되면 호출하는 곳에서 해결 할 수 있게
# => raise를 else에 넣음.
- if 문과 함께 구현해야 함.
- raise Exception()
- Exception 클래스로 해도 구현 가능하지만, 구체적으로 무슨 에러인지 모름.
- 그래서 Exception을 상속받아 내가 처리하고 싶은 예외 클래스를 정의를 한다.
_호출 (예외처리. Exception handling)
set_month(10) # 정상 return
set_month(1) # 정상 return
try:
set_month(-10) # else 블럭의 raise에 의해 -> 예외 발생
except InvalidMonthException as e: # 예외처리 실행
# 사용자 정의 예외 객체가 할당된 변수 e에 의해 __str__ 메소드 호출
print("처리", e)
- 사용자 정의 예외 객체가 할당된 변수 e에 의해 __str__ 메소드 호출. 블럭에 있는 return 실행.
=> 이 변수를 통해 원하는 출력 오류 메시지를 받을 수 있다.
_출력
10월을 설정했습니다.
1월을 설정했습니다.
처리 월은 1_ 12 사이의 정수만 가능합니다. 사용된 월은: -10입니다.
EX. 실전 예제 : 주문 처리하는 코드
class NotEnoughStockException(Exception):
# 주문량, 재고량을 Attribute로 저장.
def __init__(self, order_amount, stock_amount):
self.order_amount = order_amount
self.stock_amount = stock_amount
# 문자열로 변환 시 왜 발생했는 지를 반환.
def __str__(self):
return f"재고량:{self.stock_amount} 주문량:{self.order_amount}으로 주문하실 수 없습니다."
def order(order_amount:int):
"""
주문 처리하는 함수
[parameter]
order_amount: int -> 주문량
[return]
[Exception]
NotEnoughStockException: 주문량이 재고량보다 많으면 발생.
"""
stock_amount = 10 # 재고량
if stock_amount >= order_amount :
print("주문처리 시작")
stock_amount -= order_amount # 재고량 변경
print("주문정보 저장")
print("주문완료! 남은 재고량:", stock_amount)
else:
raise NotEnoughStockException(order_amount, stock_amount)
try:
order(30)
print("주문 후 처리할 것")
except NotEnoughStockException as o:
print("1차 주문 실패!")
print(o)
print("남은 재고량으로 주문됩니다. 재고량:", o.stock_amount)
order(o.stock_amount)
print("다음 단계 작업")
1차 주문 실패!
재고량:10 주문량:30으로 주문하실 수 없습니다.
남은 재고량으로 주문됩니다. 재고량: 10
주문처리 시작
주문정보 저장
주문완료! 남은 재고량: 0
다음 단계 작업
'AI_STUDY > Python' 카테고리의 다른 글
Python _DB : pymysql 을 이용해 mysql 연동 (0) | 2022.06.04 |
---|---|
아나콘다 가상환경 생성 및 활성화 (Python, Windows) & 패키지 pip 설치 (pandas jupyter matplotlib) (0) | 2022.06.03 |
python 프로그래밍 : 정규표현식 (0) | 2022.05.23 |
python 프로그래밍 : 텍스트 파일 입출력 (0) | 2022.05.19 |
python 프로그래밍 : 자료 구조_리스트 (0) | 2022.05.16 |