본문 바로가기
Python

나도코딩 파이썬 10장 - 예외 처리

by 햣둘 2023. 3. 31.

10.1 예외 처리하기

10.1.1 예외 처리란

예상치 못한 실수 또는 잘못된 무언가를 오류(error)라고 하며, 오류 상황에 대처하는 것을 예외처리라고 한다.

 

10.1.2 예외 처리하기 : try-except문

오류가 발생할 때 예외 처리는 다음 형식으로 작성한다.

try:

    실행할 명령1

    실행할 명령2

    ...

except 오류 종류:

    예외 처리 명령1

    예외 처리 명령2

    ...

먼저 실행하려는 코드 위에 try키워드를 적고 콜론을 붙인다. 다음 줄부터는 실행하려는 코드를 모두 들여쓴다.

그 아래 except 키워드를 적고 뒤에 어떤 오류에 대한 예외 처리인지 명시하고 콜론을 붙인다. 다음 줄부터는 예외 처리를 수행할 명령문들을 들여써서 작성한다. 

 

사용자로부터 두 수를 입력받아 나누기한 결과를 출력하는 계산기 프로그램을 예로 들겠다.

try문의 하위에 있는 명령문을 실행하다가 오류가 발생하면 프로그램을 종료하지 않고 except문의 오류 종류와 일치하는지 확인하고 일치하면 except문의 하위 명령문들이 실행된다. 만약 오류가 발생하지 않으면 except문은 실행하지 않고 넘어간다.

try:
    print("나누기 전용 계산기입니다.")
    num1 = int(input("첫 번째 숫자 : "))
    num2 = int(input("두 번째 숫자 : "))
    print("{0}/{1} = {2}".format(num1, num2, int(num1/num2)))
except ValueError: #숫자를 입력하지 않고 문자 등 다른 값을 입력한 경우
    print("오류 발생! 잘못된 값을 입력했습니다.")

 

10.1.3 오류 메시지를 예외 처리로 출력하기 : as

위에서 숫자를 입력하지 않고 문자와 같은 다른 값을 입력한 경우 ValueError가 발생했다.

이번엔 나누는 값에 0을 입력하게 되면 ZeroDivisionError : division by zero 가 발생한다.

그런데 예외 처리마다 예외처리명령 메시지를 직접 작성하려니 귀찮다.

이번에는 오류가 발생할 때 표시하는 오류 메시지를 가져와 출력하도록 예외 처리를 하겠다.

이를 위해 예외 처리 형식에서 except 뒤에 as 키워드와 변수명을 추가한다.

 

앞의 코드에서 ZeroDivisionError에 대한 예외 처리를 추가해보자. ZeroDivisionError에 대한 except 문을 작성하고 뒤에 as 키워드와 err라는 이름의 변수를 넣고 콜론을 붙인다. 그리고 변수를 print()문으로 출력한다.

try:
    print("나누기 전용 계산기입니다.")
    num1 = int(input("첫 번째 숫자 : "))
    num2 = int(input("두 번째 숫자 : "))
    print("{0}/{1} = {2}".format(num1, num2, int(num1/num2)))
except ValueError:
    print("오류 발생! 잘못된 값을 입력했습니다.")
except ZeroDivisionError as err :
    print(err)

실행해서 6과 0을 입력하면 간단한 문구를 출력하고 프로그램을 종료한다.

문구를 살펴보면 예외 처리 전에 발생한 오류 메시지 중 ZeroDivisionError : 뒤에 나오는 division by zero 내용이다.

이와 같이 어떤 문제인지 쉽게 알아볼 수 있는 메시지가 제공되는 오류는

따로 예외 처리 메시지를 정의하지 않고도 간편하게 예외 처리를 할 수 있다.

 

이번에는 try구문을 수정해보겠다. nums라는 리스트를 정의해서 입력받은 두 숫자를 리스트의 0,1번 인덱스 자리에 넣고 연산결과는 리스트의 2번 인덱스에 추가한다. 그런데 만약 연산결과를 리스트에 추가하는 부분을 주석처리 해버린다면?

IndexError라는 새로운 오류가 발생한다. 그런데 이렇게 모든 오류에 대한 예외처리를 작성하면 코드가 한없이 길어진다.

try:
    print("나누기 전용 계산기입니다.")
    nums = [] #리스트 정의
    nums.append(int(input("첫 번째 숫자 : ")))
    nums.append(int(input("두 번째 숫자 : ")))
    #nums.append(int(nums[0]/nums[1])) #연산결과를 리스트에 추가
    print("{0}/{1} = {2}".format(nums[0], nums[1], nums[2])))
except ValueError:
    print("오류 발생! 잘못된 값을 입력했습니다.")
except ZeroDivisionError as err :
    print(err)

 

그래서 코드 마지막에 다음과 같이 구문을 추가하면 지금까지 정의하지 않은 모든 오류를 예외처리할 수 있다.

Exception은 앞에 나온 예외 처리 클래스들의 부모 클래스이다.

try:
    print("나누기 전용 계산기입니다.")
    nums = [] #리스트 정의
    nums.append(int(input("첫 번째 숫자 : ")))
    nums.append(int(input("두 번째 숫자 : ")))
    #nums.append(int(nums[0]/nums[1])) #연산결과를 리스트에 추가
    print("{0}/{1} = {2}".format(nums[0], nums[1], nums[2]))
except ValueError:
    print("오류 발생! 잘못된 값을 입력했습니다.")
except ZeroDivisionError as err :
    print(err)

except Exception as err:
    print("알 수 없는 오류가 발생했습니다.")
    print(err)

 

 

10.2 오류 발생시키기

지금까지 발생한 오류는 모두 어떨 때 오류가 발생하는지 파이썬에 형태가 미리 정의되어 있었다. 그런데 직접 작성한 프로그램에서 허용하지 않는 동작을 하려고 할 때도 의도적으로 오류를 발생시킬 수 있다.

형식 : raise 오류 종류

try:
    print("한 자리 숫자 나누기 전용 계산기입니다.")
    num1 = (int(input("첫 번째 숫자 : ")))
    num2 = (int(input("두 번째 숫자 : ")))
    if num1 >= 10 or num2 >= 10: #입력받은 숫자가 한 자리가 아니라면
        raise ValueError #오류 발생시킴
    print("{0}/{1} = {2}".format(num1, num2, int(num1/num2)))
except ValueError:
    print("값을 잘못 입력했습니다. 한 자리 숫자만 입력하세요.")

if문에 의해 ValueError가 발생하고 이에 따라 예외 처리 구문이 실행돼 print()문의 내용을 출력한다.

 

10.3 사용자 정의 예외 처리하기

앞에서 프로그램 안에서 의도적으로 오류를 발생시키는 방법을 배웠다. 하지만 이것도 파이썬에 이미 정의되어있는 오류를 가져다 사용한 것이다. 이미 정의된 오류 말고도 사용자가 직접 오류를 정의해서 예외 처리를 할 수도 있다.

 

두 자리 이상의 수를 입력했을 때 발생하는 오류라는 의미로 BigNumberError라는 클래스를 만들자. 그리고 코드에서 새로운 오류를 정의해 예외처리 하려면 파이썬에 포함된 Exception이라는 클래스를 상속해야 한다. 

__init__() 생성자에서는 오류 메시지를 의미하는 msg를 전달받아 인스턴스 변수로 설정한다.

__str__() 메서드에서는 인스턴스 변수 msg를 반환하게 한다.

 

try문에서 BigNumberError를 발생시키는 부분에 입력받은 두 값을 문자열 형태로 넣는다.

이 문자열은 __init__()생성자의 msg로 들어가게 되고

그런 다음 __str__()메서드에 의해 msg 인스턴스 변수가 반환된다.

그리고 except문에서는 as를 이용해 err라는 이름으로 반환된 오류 내용을 받고 

이를 print()문으로 출력한다.

class BigNumberError(Exception): #사용자가 직접 정의한 예외 처리, Exception클래스를 상속함
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg

try:
    print("한 자리 숫자 나누기 전용 계산기입니다.")
    num1 = (int(input("첫 번째 숫자 : ")))
    num2 = (int(input("두 번째 숫자 : ")))
    if num1 >= 10 or num2 >= 10: #입력받은 숫자가 한 자리가 아니라면
        raise BigNumberError("입력값 : {0}, {1}".format(num1, num2))
    print("{0}/{1} = {2}".format(num1, num2, int(num1/num2)))
except ValueError:
    print("값을 잘못 입력했습니다. 한 자리 숫자만 입력하세요.")
except BigNumberError as err: #사용자 정의 예외 처리
    print("오류가 발생했습니다. 한 자리 숫자만 입력하세요.")
    print(err) #오류 메시지 출력

이제는 오류 내용과 함께 사용자가 어떤 값을 입력했는지도 함께 출력한다.

 

* 스페셜 메서드

__init__()나 __str__()처럼 이름 앞뒤로 언더바가 2개씩 붙은 형태의 메서드를 스페셜 메서드(또는 던더 메서드)라고 한다. 이들은 특별한 역할을 수행하기 위해 별도 처리를 하는 메서드이다. __init__()메서드는 객체가 생성될 때 자동으로 호출되고, __str__()메서드는 print()함수로 객체를 출력할 때 호출된다.

class SpecialClass():
    def __init__(self):
        print("특별한 생성자")
    def __str__(self):
        return "특별한 메서드"
s = SpecialClass() #특별한 생성자
print(s) #특별한 메서드

객체가 생성될 때 __init__()메서드가 호출되어 "특별한 생성자"가 출력된다. 

그리고 print()함수로 객체 s를 출력하면 __str__()메서드가 호출되어 "특별한 메서드"가 출력된다.

이외에도 객체 길이를 구할 때 호출되는 __len__(),

객체가 특정 요소를 포함하는지 확인할 때 호출되는 __contains__() 등이 있다.

 

 

10.4 오류와 상관 없이 무조건 실행하기 : finally

finally는 try문에서 오류가 발생하든 말든 try문을 벗어나는 시점에 무조건 실행되는 구문이다.

finally는 try와 except로 이루어진 구문의 가장 밑에 정의한다.

class BigNumberError(Exception): 
	pass

try:
    pass
except ValueError:
    pass
except BigNumberError as err: 
	pass

finally:#오류 발생 여부와 상관없이 항상 실행
    print("계산기를 이용해주셔서 감사합니다.")

 

보통 try문에서 파일이나 자원을 사용할 때 finally문에서 열린 파일을 닫거나 자원을 해제하는 작업을 수행한다.

그러면 프로그램이 실행되는 과정에서 오류가 발생하고 예외 처리가 제대로 되지 않더라도 자원은 정상적으로 해제할 수 있다.

 

10.5 실습 문제 : 치킨 주문하기

치킨 가게 - 자동 주문 프로그램
1보다 작거나, 숫자가 아닌 입력값이 들어오면 ValueError로 처리
order <= 10
치킨 다 떨어졌으면 SoldOutError 발생시킨 후 프로그램 종료

class SoldOutError(Exception):
    pass
chicken = 10 # 남은 치킨 수
waiting = 1 # 대기번호 1번부터 시작

while True:
    try:
        print("[남은 치킨 : {0}]".format(chicken))
        order = int(input("치킨 몇 마리 주문? : "))
        if order > chicken: # 남은 치킨보다 주문량이 더 많을 때
            print("재료가 부족합니다.")
        elif order <= 0:# 입력값이 1보다 작다면
            raise ValueError # 오류 발생시킴
        else:
            print("[대기번호 {0}] {1}마리를 주문했습니다.".format(waiting, order))
            waiting += 1 # 대기번호 1 증가
            chicken -= order # 주문 수만큼 남은 치킨 감소
        if chicken == 0:# 남은 치킨 수가 0이 되면
            raise SoldOutError # 재료소진으로 주문 불가 -> 오류 발생시킴
    except ValueError: # 입력값 잘못 입력 시 발생하는 오류의 예외 처리
        print("잘못된 값을 입력했습니다.")
    except SoldOutError:#재료 소진 시 발생하는 오류의 예외 처리
        print("재료가 소진되어 더이상 주문받지 않습니다.")
        break # 이제 더이상 주문을 받을 수 없으므로 break를 통해 while문 탈출

 

'Python' 카테고리의 다른 글

List Comprehensions  (2) 2025.06.23
나도코딩 파이썬 11장 - 모듈과 패키지  (0) 2023.03.31
나도코딩 파이썬 9장 - 클래스  (0) 2023.03.27
나도코딩 파이썬 8장 - 입출력  (0) 2023.03.24
나도코딩 파이썬 7장 - 함수  (0) 2023.03.22