파이썬 강좌 – 예외와 에러 – 예상치 못한 상황에 대응하기

  1. 프롤로그
  2. 개발 첫걸음
    1. 컴퓨터 구성요소 – 컴퓨터는 어떤 걸 할 수 있나?
    2. 개발과 관련된 용어
    3. 파이썬의 선택 – 왜 파이썬인가?
    4. 파이썬 설치 – Hello World 출력하기
    5. Visual Studio Code 의 편리한 기능
    6. REPL과 콘솔 창 – 파이썬 동작시키기
  3. 파이썬 기초
    1. 기초 입출력 – 소통하기
    2. 변수와 대입 – 기억하기
    3. 연산자 – 계산하기
    4. 조건문 – 분기를 만들기
    5. 반복문 – 비슷한 작업을 반복하기
    6. 반복문 코딩하기
    7. 변수와 리스트 – 비슷한 변수들을 묶기
    8. for, range – 리스트의 항목을 다루기
    9. 함수와 메소드의 호출 – 편리한 기능 이용하기
    10. 모듈 설치와 사용 – 유용한 기능 끌어다 쓰기
    11. 문자열 – 텍스트 다루기
  4. 파이썬 중급
    1. 함수를 직접 만들기 – 자주 쓰는 기능을 묶기
    2. 딕셔너리, 튜플, 세트 – 변수를 다양한 방법으로 묶기
    3. 클래스와 객체 – 변수를 사람으로 진화시키기
    1. 상속 – 클래스를 확장하기
    2. 정체성과 동질성 – 객체의 성질
    3. 특별 메소드와 연산자 – 파이썬의 내부 작동방식 이해하기
    4. 다양한 함수 인수 – 유연한 함수 만들기
    5. 슬라이싱 – 리스트 간편하게 접근하기
    6. 지능형 리스트(List Comprehension) – 리스트 갖고 놀기
    7. namedtuple - 데이터 묶음 손쉽게 만들기
    8. 조건 표현식 (Conditional Expression) - 간단한 분기 나타내기
    9. 코드 스타일 - 코드의 일관성 유지하기
    10. 명령문, 표현식 – 문법을 이루는 것들
    11. 본격적인 검색 해보기
  5. 파이썬 고급
    1. 일급 함수 다루기
    2. NotImplementedError와 NotImplemented
    3. 어노테이션 – 수월하게 프로그래밍하기
    1. 내장 함수 톺아보기
    2. 예외와 에러 – 예상치 못한 상황에 대응하기 (v0.1)
    3. 변수의 범위 – 이름 검색의 범위
  6. 파이썬 심화
    1. 시퀀스와 반복자 – 반복과 순회를 자유자재로 다루기
    2. 데코레이터 – 함수의 기능을 강화하기
    3. 프로퍼티
    4. 제너레이터
    5. async와 await
    6. 객체로서의 클래스 – 클래스를 동적으로 정의하기
  7. 파이썬 프로젝트 실습
    1. 원카드 게임 만들기 (1)
    2. 원카드 게임 만들기 (2)
    3. 원카드 게임 만들기 (3) (작성중)
    4. 턴제 자동 전투 게임 만들기 (작성중)
  8. 실전 (파이썬 외적인 것들)
    1. 정규표현식 – 문자열을 검색하고 치환하기 (작성중)
    2. 유니코드 – 컴퓨터에서 문자를 표기하는 방법
    3. html, css, 인터넷 – 자동화 첫 걸음 내딛기
    4. 네트워크 – 인터넷으로 통신하는 방법
    5. 문서 – 문맥을 읽어보기

에러란?

롤하다 에러로 종료되면 짜증나쥬

뭔가 정상적으로 되지 않아서 종료되는 상황입니다. 서문은 빠르게 생략합니다.


프로그램이 실행되기 전에 발생하는 에러

당장 에러를 만들어봅시다.

hi?
  File "c:/Users/tooth/Desktop/test2.py", line 1
    hi?
      ^
SyntaxError: invalid syntax

파이썬 인터프리터가 파일을 대충 슥 훑어보고 아, 이건 영 아니다 싶은 건 바로 SyntaxError을 냅니다. 문법이 잘못되었다는 뜻이죠. 이건 아예 실행조차 되지 않는 상태입니다. 들여쓰기를 잘못해서 발생하는 IndentationError도 실행되지 않는 건 마찬가지입니다. 에러 메시지에서 몇 라인인지 알려주므로 그 쪽으로 가서 고쳐주도록 합니다.


프로그램 실행 중에 발생하는 에러

사실 우리가 다루고자 하는 에러는 대부분 프로그램 실행 중에 발생합니다.

ls = []
ls[4]
Traceback (most recent call last):
  File "c:/Users/tooth/Desktop/test2.py", line 2, in <module>
    ls[4]
IndexError: list index out of range

일단 문법적으로 보았을 때 하자가 없기 때문에 파일이 정상적으로 실행됩니다. 그리고 첫 번째 행인 ls = []는 제대로 작동합니다. 하지만 두 번째 행인 ls[4]에서는 에러가 발생합니다. 빈 배열이기 때문에 4번째 항목에 접근하는 것 자체가 어불성설입니다. 프로그램은 바로 종료됩니다.

문법 에러와는 다르게, 프로그램 실행 중에 발생하는 에러는 Traceback이라는 것을 보여줍니다. 에러 메시지에서 Traceback (most recent call last) 를 보여주는 이유는, 오류를 발생시킨 함수 호출의 역추적한 내용을 보여주어 프로그래머가 한층 더 쉽게 에러를 수정할 수 있게 하기 위함입니다. Traceback의 단계는 함수 단위인데요, 여러 함수에 걸쳐져 있을 경우 어떻게 보이는지 테스트해봅시다.

def a():
    ls = []
    ls[4]

def b():
    a()

def c():
    b()

def d():
    c()

def e():
    d()

e()
Traceback (most recent call last):
  File "c:/Users/tooth/Desktop/test2.py", line 17, in <module>
    e()
  File "c:/Users/tooth/Desktop/test2.py", line 15, in e
    d()
  File "c:/Users/tooth/Desktop/test2.py", line 12, in d
    c()
  File "c:/Users/tooth/Desktop/test2.py", line 9, in c
    b()
  File "c:/Users/tooth/Desktop/test2.py", line 6, in b
    a()
  File "c:/Users/tooth/Desktop/test2.py", line 3, in a
    ls[4]
IndexError: list index out of range

에러는 a 함수에서 발생했고, a 함수를 실행시킨 주체를 역추적해나가서 최종적으로 e 함수의 호출 부분까지 전부 보여줍니다.


에러 핸들링

예상치 못한 상황에 발생하는 것이 에러입니다. 하지만 예상치 못한 상황에 도래한다고 해서 무조건 프로그램을 종료시켜야 할까요? 모든 세세한 사항을 전부 예측하여 에러가 발생할 구멍을 다 막는 완전고결한 프로그램을 만들어야 할까요? 파이썬의 철학은 그런 방향은 아닙니다. 오히려 일단 실행시켜보고, 에러가 나면 적당히 처리한다 가 주된 흐름입니다. “에러를 처리한다“는 “에러를 핸들링한다“, “예외를 처리한다” 라는 말과 똑같은 뜻입니다.

아니, 이때까지의 프로그램은 에러가 발생하면 프로그램이 무조건 종료되는데, 처리할 틈이 어디 있다고 그러시나요? 파이썬에서는 프로그램이 종료되기 전에 에러를 처리할 유예를 둡니다. 아주 강력하게요. 그 문법은 바로 try ~ except ~ else ~ finally ~ 입니다.

본격적으로 넘어가기 전에, 에러와 예외는 같은 뜻이라고 보시면 됩니다.


간단한 예제

간단한 예제를 살펴보며 예외를 어떻게 처리하는지 대강 살펴보도록 합시다. 다음 예제는 Person이라는 클래스를 정의하고 있습니다. Person에서는 age를 속성으로 가지고 있으며, add_age 메소드를 통해 age에 값을 더하고 있습니다. 하지만 add_age를 호출할 때에 인수로 숫자형이 아니라 문자열이 들어가게 된다면 어떻게 될까요?

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def add_age(self, age):
        self.age += age

p1 = Person('James', 15)
p1.add_age('ho')

print("프로그램의 끝입니다.")
Traceback (most recent call last):
  File "c:/Users/tooth/Desktop/test2.py", line 10, in <module>
    p1.add_age('ho')
  File "c:/Users/tooth/Desktop/test2.py", line 7, in add_age
    self.age += age
TypeError: unsupported operand type(s) for +=: 'int' and 'str'

예. 보시다시피 에러가 납니다. 에러가 나는 것은 당연합니다. 숫자에 어떻게 문자열을 더하나요. 프로그램의 끝입니다는 출력도 되지 않았습니다. 에러가 나는 순간 프로그램이 종료되었기 때문입니다.


이제 우리는 단순한 프로그램 종료가 아니라 에러를 적절하게 처리하고자 합니다. 이제 새로운 문법을 사용할 차례입니다. 바로 tryexcept 입니다! 코드를 수정해봅시다.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def add_age(self, age):
        try:
            self.age += age
        except:
            print("올바른 숫자를 넣어주세요!")

p1 = Person('James', 15)
p1.add_age('ho')

print("프로그램의 끝입니다.")
올바른 숫자를 넣어주세요!
프로그램의 끝입니다.

와우! 프로그램이 끝나지 않았습니다. 프로그램의 끝입니다도 제대로 출력되었습니다. try ~ except 구문에서 에러가 잡힌다면 에러를 적절히 처리했다고 간주하고 프로그램을 계속 진행시키는 파이썬 인터프리터의 모습을 보실 수 있습니다~~!


문법 설명

우선 가장 간단한 형태는 다음과 같으며,

try:
    pass # 예외가 발생할 법한 코드
except: 
    pass # 예외가 발생했을 때 실행할 코드

완전한 전체 형태는 다음과 같습니다.

try:
    pass # 예외가 발생할 법한 코드
except expression as identifier: 
    pass # 예외가 발생했을 때 실행할 코드
else: 
    pass # 예외 발생 없이 무사히 try가 실행되었을 때 실행할 코드
finally:
    pass # except 또는 else 절 실행 후 마지막으로 실행할 코드

except 부분을 집중적으로 봅시다. 여기는 다음의 세 가지로 용법으로 사용할 수 있습니다.

  • except:어떤 예외든 상관없이 모두 받아 적절히 처리하겠다는 뜻입니다.
  • except SomeException:에러의 종류가 SomeException 인 경우에, 하지만 에러 정보는 받을 필요 없이 적절히 에러 처리만 하겠다는 뜻입니다. 이 구문은 여러 번 사용할 수 있습니다.
  • except SomeException as identifier:의 뜻은, 예외의 종류를 SomeException으로 지정하고, 에러에 관한 정보를 identifier이라는 변수로 받겠다는 뜻입니다. 이 구문도 여러 번 사용할 수 있습니다.

elsefinally도 각각 독립적으로 선택 사항입니다. 실제로 코드를 만들어보는 연습을 하려면 프로그래밍 문제를 이용하도록 합니다.


처음에 다루었던 예제로 에러 메시지를 한번 출력해보도록 합시다. except를 가장 복잡한 형태로 써보도록 합시다. 참고로 Exception 클래스는 종료 시그널을 제외한 모든 예외나 에러의 조상 클래스입니다. 에러 정보를 변수로 받아 print에 그대로 출력하면 에러 메시지를 확인할 수 있습니다.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def add_age(self, age):
        try:
            self.age += age
            print("현재 나이는", self.age, "입니다.")
        except Exception as e:
            print("올바른 숫자를 넣어주세요! >>", e)

p1 = Person('James', 15)
p1.add_age('ho')
p1.add_age(10)
print("프로그램의 끝입니다.")
올바른 숫자를 넣어주세요! >> unsupported operand type(s) for +=: 'int' and 'str'
현재 나이는 25 입니다.
프로그램의 끝입니다.

좋습니다.


에러 만들고 발생시키기

아니, 에러를 만든다니 그게 무슨 소리입니까? 말 그대로입니다. 뭔가 예상대로 동작하진 않지만 에러가 없이 동작할 때도 있을 겁니다. 예를 들어 나이를 더하려고 하는데 소수가 들어왔습니다. 소수랑 정수는 잘 더해지죠! 이를 그대로 잠자코 두고 보아야 할까요?

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def add_age(self, age):
        try:
            self.age += age
            print("현재 나이는", self.age, "입니다.")
        except Exception as e:
            print("올바른 숫자를 넣어주세요! >>", e)

p1 = Person('James', 15)
p1.add_age('ho')
p1.add_age(10)
p1.add_age(12.3)
print("프로그램의 끝입니다.")
올바른 숫자를 넣어주세요! >> unsupported operand type(s) for +=: 'int' and 'str'
현재 나이는 25 입니다.
현재 나이는 37.3 입니다.
프로그램의 끝입니다

나이를 도저히 소수점으로 두고 싶지 않습니다.


여기서 두 가지 방법이 있습니다. 우선 첫 번째는, 함수의 인수로 들어온 ageint로 강제 형변환 하여 사용하는 것입니다.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def add_age(self, age):
        try:
            self.age += int(age)
            print("현재 나이는", self.age, "입니다.")
        except Exception as e:
            print("올바른 숫자를 넣어주세요! >>", e)

p1 = Person('James', 15)
p1.add_age('ho')
p1.add_age(10)
p1.add_age(12.3)
print("프로그램의 끝입니다.")
올바른 숫자를 넣어주세요! >> invalid literal for int() with base 10: 'ho'
현재 나이는 25 입니다.
현재 나이는 37 입니다.
프로그램의 끝입니다.
PS C:\Users\tooth>

이렇게 하면 기능 상에 아무런 문제가 없습니다. 이 정도로 타입 변환만 해줘도 할 일은 다 했다고 보면 됩니다. 어차피 문자열은 타입 변환인 int(age)에서도 에러가 발생하니까, 뭔가 예상되지 않은 일이 벌어질 일은 없습니다.

하지만 Person 클래스를 이용하는 클라이언트가 아주 많아서 클래스를 아주 세밀하게 구성해야 한다면, 이야기가 조금 달라집니다. 에러 메시지를 한 번 살펴봅시다. 'ho'는 10 진수 정수로 바꿀 수 없는 잘못된 리터럴이라 되어있습니다. 함수의 내부를 모르는 사람 입장에서는 그렇게 직관적인 에러 메시지가 아닙니다.


그러므로 에러 메시지를 확실히 하기 위해 새로운 에러를 발생시켜 보자구요. 에러는 raise로 만들어낼 수 있습니다. 또한 age가 어떤 타입인지 체크하기 위해 isinstance 내장 함수와 numbers 모듈을 이용할 것입니다.

from numbers import Integral

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def add_age(self, age):
        try:
            if not isinstance(age, Integral):
                raise TypeError("정수가 입력되지 않았어요!!")
    
            self.age += age
            print("현재 나이는", self.age, "입니다.")
        except Exception as e:
            print("올바른 숫자를 넣어주세요! >>", e)

p1 = Person('James', 15)
p1.add_age('ho')
p1.add_age(10)
p1.add_age(1.3)
print("프로그램의 끝입니다.")
올바른 숫자를 넣어주세요! >> 정수가 입력되지 않았어요!!
현재 나이는 25 입니다.
올바른 숫자를 넣어주세요! >> 정수가 입력되지 않았어요!!
프로그램의 끝입니다.

위 예제는 오로지 연습용으로 만든 예제이므로 다소 실용적이지 않을 수 있습니다. 하지만 이렇게 어떤 타입인지 검사하는 것은 예외나 에러를 다루는 것과 밀접하게 연관되어 있습니다. 타입 검사와 관련된 모듈은 collections.abcnumbers 등이 있습니다.

보시다시피 raise의 기본적인 사용법은 raise 에러타입("에러메시지") 입니다.

아까 파이썬의 철학에 대해 조금 언급할 때 일단 실행시켜보고, 에러가 나면 적당히 처리한다라고 했었죠? 이것은 만들어져 있는 라이브러리나 클래스나 다양한 기능을 실행시키는 입장인 것입니다. 본격적인 개발자가 아니라면 대부분 만들어져 있는 기능을 사용하는 데만 바쁘기 때문에 에러를 직접 발생시키거나 하는 일이 거의 없습니다.

반면 다른 우둔한 백성들이 이용할 만한 모듈이나 클래스를 만드는 똑똑한 사람이 되면 입장차가 조금 생깁니다. 바로 내 선에서 해결할 수 없는 문제는 에러를 발생시켜서 책임을 전가시킨다. 입니다. 이 때는 raise를 적극적으로 활용해볼 여지가 있습니다. 단, 에러 메시지를 논리정연하게 정리해 주어야겠지요.


Traceback 직접 다뤄보기

(추가 예정)


연습 문제

(추가 예정)


프로그래밍 문제

(추가 예정)


프로그래밍 문제 정답

(추가 예정)

  1. 프롤로그
  2. 개발 첫걸음
    1. 컴퓨터 구성요소 – 컴퓨터는 어떤 걸 할 수 있나?
    2. 개발과 관련된 용어
    3. 파이썬의 선택 – 왜 파이썬인가?
    4. 파이썬 설치 – Hello World 출력하기
    5. Visual Studio Code 의 편리한 기능
    6. REPL과 콘솔 창 – 파이썬 동작시키기
  3. 파이썬 기초
    1. 기초 입출력 – 소통하기
    2. 변수와 대입 – 기억하기
    3. 연산자 – 계산하기
    4. 조건문 – 분기를 만들기
    5. 반복문 – 비슷한 작업을 반복하기
    6. 반복문 코딩하기
    7. 변수와 리스트 – 비슷한 변수들을 묶기
    8. for, range – 리스트의 항목을 다루기
    9. 함수와 메소드의 호출 – 편리한 기능 이용하기
    10. 모듈 설치와 사용 – 유용한 기능 끌어다 쓰기
    11. 문자열 – 텍스트 다루기
  4. 파이썬 중급
    1. 함수를 직접 만들기 – 자주 쓰는 기능을 묶기
    2. 딕셔너리, 튜플, 세트 – 변수를 다양한 방법으로 묶기
    3. 클래스와 객체 – 변수를 사람으로 진화시키기
    1. 상속 – 클래스를 확장하기
    2. 정체성과 동질성 – 객체의 성질
    3. 특별 메소드와 연산자 – 파이썬의 내부 작동방식 이해하기
    4. 다양한 함수 인수 – 유연한 함수 만들기
    5. 슬라이싱 – 리스트 간편하게 접근하기
    6. 지능형 리스트(List Comprehension) – 리스트 갖고 놀기
    7. namedtuple - 데이터 묶음 손쉽게 만들기
    8. 조건 표현식 (Conditional Expression) - 간단한 분기 나타내기
    9. 코드 스타일 - 코드의 일관성 유지하기
    10. 명령문, 표현식 – 문법을 이루는 것들
    11. 본격적인 검색 해보기
  5. 파이썬 고급
    1. 일급 함수 다루기
    2. NotImplementedError와 NotImplemented
    3. 어노테이션 – 수월하게 프로그래밍하기
    1. 내장 함수 톺아보기
    2. 예외와 에러 – 예상치 못한 상황에 대응하기 (v0.1)
    3. 변수의 범위 – 이름 검색의 범위
  6. 파이썬 심화
    1. 시퀀스와 반복자 – 반복과 순회를 자유자재로 다루기
    2. 데코레이터 – 함수의 기능을 강화하기
    3. 프로퍼티
    4. 제너레이터
    5. async와 await
    6. 객체로서의 클래스 – 클래스를 동적으로 정의하기
  7. 파이썬 프로젝트 실습
    1. 원카드 게임 만들기 (1)
    2. 원카드 게임 만들기 (2)
    3. 원카드 게임 만들기 (3) (작성중)
    4. 턴제 자동 전투 게임 만들기 (작성중)
  8. 실전 (파이썬 외적인 것들)
    1. 정규표현식 – 문자열을 검색하고 치환하기 (작성중)
    2. 유니코드 – 컴퓨터에서 문자를 표기하는 방법
    3. html, css, 인터넷 – 자동화 첫 걸음 내딛기
    4. 네트워크 – 인터넷으로 통신하는 방법
    5. 문서 – 문맥을 읽어보기

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다

Scroll to top