[7장] 공식 문서로 알아보는 Python 예외 처리의 모든 것

박주원
July 19, 2025

[7장] 공식 문서로 알아보는 Python 예외 처리의 모든 것

Use Original Cover Image
Type
Post
Children
Language
ko
Tags
Python
Exception
try
except
finally
with
ExceptionGroup
add_note
Authors
박주원
Published
July 19, 2025

1. 에러 vs 예외

1.1 Error

while True print('Hello world')
page icon
File "path/to/folder/code.py", line 1 while True ^ SyntaxError: expected ':'
에러라는 것은 프로그램의 시스템 차원에서 발생하는 치명적인 오류로, 만약 프로그램에 에러가 있다면 그 프로그램은 컴파일 자체가 불가능하며, 즉 실행이 불가능하다.
Python에 존재하는 에러로는 SyntaxError 와 그것의 자식 클래스들이 있다.
SyntaxErrorwhile문에 :를 빼먹는 것과 같이, Python 코드가 문법에 맞지 않아 발생하는 에러이다.
 

1.2 Exception

10 * (1/0)
 
page icon
Traceback (most recent call last): File "path/to/folder/code.py", line 1, in <module> 10 * (1/0) ~^~ ZeroDivisionError: division by zero
4 + spam*3
page icon
Traceback (most recent call last): File "path/to/folder/code.py", line 1, in <module> 4 + spam*3 ^^^^ NameError: name 'spam' is not defined
'2' + 2
 
page icon
Traceback (most recent call last): File "path/to/folder/code.py", line 1, in <module> '2' + 2 ~~~~^~~ TypeError: can only concatenate str (not "int") to str
예외라는 것은 프로그램의 런타임(실행 중)에서 잠정적으로 발생할 수 있는 오류로, 예외가 발생할 수 있더라도 프로그램은 컴파일이 가능하며, 곧 실행이 가능하다.
프로그램의 실행 중에 처리되지 않은 예외가 발생한다면, 프로그램은 오류 메세지를 출력하고 실행을 중지한다.
Python에 존재하는 예외로는 ZeroDivisionError , NameError , TypeError 등의 수많은 종류의 예외들이 있다.
그리고 이러한 built-in 예외 말고도, 사용자가 직접 예외를 정의할 수도 있다.
Python에서의 예외들은 에러가 아닌 예외이더라도 이름에 Error가 붙는 경우가 많다.
 

1.3 Error vs Exception

에러와 예외의 차이를 표로 정리하면 다음과 같다:
Error
Exception
프로그램 실행 가능성
Error가 발생하면 실행 불가능
Exception이 발생할 수 있어도 실행은 가능
발생 타이밍
컴파일, 실행 전
런타임, 실행 중
발생 시 영향
프로그램 컴파일 불가
프로그램 실행 중지
Python에서의 예시
SyntaxError 와 그것의 자식 클래스
NameError 등의 built-in 클래스와 사용자 정의 예외 클래스
 

2. Exception Handling

2.1 tryexcept

while True: try: x = int(input("숫자를 입력해주세요: ")) break except ValueError: print("유효한 숫자가 아닙니다. 다시 시도해주세요...")
page icon
숫자를 입력해주세요: asdf 유효한 숫자가 아닙니다. 다시 시도해주세요... 숫자를 입력해주세요: 3
Python에서의 예외 처리는 try문과 except문을 통해 할 수 있다.
만약 프로그램이 try 문을 만나면 try문 안의 코드 블록을 실행하고, 그 코드를 실행하는 중에 예외가 어떤 형식으로 발생하냐에 따라 다음과 같이 프로그램의 처리가 달라진다:
  • 만약 try문 안에서 예외가 발생하지 않으면, 프로그램은 그대로 try문 바깥의 다음 코드를 진행한다.
  • 만약 try문 안에서 예외가 발생하고, 그 예외가 try문에 엮인 except문에 붙은 예외 클래스와 매칭된다면, 프로그램은 try문의 실행을 중단하고 해당 except문을 실행한다.
  • 만약 try문 안에서 예외가 발생했지만 그 예외가 except문에서 처리되지 않는다면, 프로그램은 실행을 중지한다.
만약 tryexcept 구조가 중첩되어 사용되었을 경우에는, except 문에서 처리되지 않은 예외가 발생하면 외부의 try 문 안에서 예외가 발생한 것으로 간주하여 외부의 except 문으로 이동한다.
 

2.2 여러 종류의 예외 매칭

try: # 코드 except (RuntimeError, TypeError, NameError): # RuntimeError, TypeError, NameError 예외 처리
try: # 코드 except RuntimeError: # RuntimeError 예외 처리 except TypeError: # TypeError 예외 처리 except NameError: # NameError 예외 처리
except 문에서는 하나의 예외 클래스에만 매칭할 수 있는 것이 아니라, 하나의 try 문에 대해 여러 개의 예외 클래스에 매칭할 수 있다.
위의 첫번째 예시는 하나의 try 문에 대해 RuntimeError , TypeError , NameError , 3개의 예외 클래스들에 매칭하는 하나의 except 문을 사용하고 있다.
두번째 예시는 하나의 try 문에 대해 각각 RuntimeError , TypeError , NameError 에 매칭하는 3개의 except 문을 사용하고 있다.
두번째 예시의 첫번째 예시와의 차이는 각각의 예외 클래스에 대해 예외 처리를 각각 따로 할 수 있다는 것이다.
 

2.3 Exceptionas

try: x = 1 / 0 except Exception as exc: print('type:', type(exc)) print('args:', exc.args) print('__str__:', exc)
page icon
type: <class 'ZeroDivisionError'> args: ('division by zero',) __str__: division by zero
Python에 존재하는 모든 예외는 BaseException 클래스를 상속하며, 프로그램에 있어 치명적이지 않은 모든 예외들은 BaseException 클래스의 자식 클래스인 Exception 을 상속한다.
except 문에서는 예외 클래스의 패턴을 매칭해서 예외를 처리하지만, 거의 모든 예외는 Exception 클래스를 상속하기 때문에 except 문에서 Exception 이라는 패턴으로 일반적인 예외들을 매칭할 수 있다.
 
또한 except 문에서 as 를 사용함으로써 발생한 예외의 클래스 인스턴스를 가져올 수 있다.
예시에서와 같이, 예외 인스턴스의 타입은 해당 예외의 클래스이고, args 속성은 해당 예외의 자세한 정보를 가지고 있는 tuple이다.
예외 인스턴스를 print 하여 __str__ 속성을 확인하면 그 인스턴스의 args 를 확인할 수 있다.
 

2.4 except 문과 상속 관계

try: # 코드 except Exception: print("Exception occured") except RuntimeError: print("RuntimeError occured") except NotImplementedError: print("NotImplementedError occured")
Python의 built-in 예외 클래스 중, NotImplementedError 클래스는 RuntimeError 클래스를 상속하고, RuntimeError 클래스는 앞서 서술했듯 Exception 클래스를 상속한다.
except 문에서는 예외 클래스를 패턴 매칭하기 때문에, Exception이라는 패턴에는 RuntimeError 이던 NotImplementedError 이던 이 패턴에 매칭된다.
따라서 위의 예시의 try 문 안에서 NotImplementedErrorRuntimeError 가 발생하여도, 제일 위의 except Exception: 에 패턴이 매칭되기 때문에 두 경우 모두 print("Exception occured") 이 실행된다.
 
try: # 코드 except NotImplementedError: print("NotImplementedError occured") except RuntimeError: print("RuntimeError occured") except Exception: print("Exception occured")
반대로 이런 코드의 경우, NotImplementedError 가 발생하면 print("NotImplementedError occured") 를 실행하고, RuntimeError 가 발생하면 print("RuntimeError occured") 를 실행한다.
 

2.5 else

try: x = int(input("숫자를 입력해주세요: ")) except ValueError: print("유효한 숫자가 아닙니다. 프로그램을 종료합니다.") else: print(f"입력한 숫자는 {x}입니다.")
page icon
숫자를 입력해주세요: asdf 유효한 숫자가 아닙니다. 프로그램을 종료합니다.
page icon
숫자를 입력해주세요: 3 입력한 숫자는 3입니다.
예외 처리에서 tryexcept 문 다음에 else 문이 올 수 있다.
else 문의 코드 블록은 해당 try 문에서 예외가 발생하지 않았을 경우에만 실행된다.
일반적으로 else 문에는 try 에서 연 파일 핸들러를 닫는 것과 같이, 예외가 발생할 수도 있는 try 문이 정상적으로 실행되었을 경우에 처리하는 코드가 들어간다.
 

3. raise

raise NameError("안녕하세요")
page icon
Traceback (most recent call last): File "path/to/folder/code.py", line 1, in <module> raise NameError("안녕하세요") NameError: 안녕하세요
Python에서는 raise 문을 통해 예외를 직접 발생 시킬 수 있다.
raise 다음에는 NameError 와 같이 특정한 예외 클래스의 인스턴스를 받아서, 해당 예외 인스턴스를 발생 시킨다.
 
try: x = int(input("양수를 입력해주세요: ")) if x <= 0: raise Exception("양수가 아닌 수가 입력되었습니다.") except Exception as exc: print("error:", exc)
page icon
양수를 입력해주세요: -8 error: 양수가 아닌 수가 입력되었습니다.
위에서 서술하였듯, 일반적인 모든 예외들은 Exception 클래스의 파생 클래스이기 때문에 raise Exception("에러 메세지") 와 같이 일반적인 예외를 발생 시킬 수도 있다.
 
try: raise NameError('안녕하세요') except NameError: print('NameError occured') raise
page icon
NameError occured Traceback (most recent call last): File "path/to/folder/code.py", line 2, in <module> raise NameError('안녕하세요') NameError: 안녕하세요
만약 except 문 안에서 예외를 재발생 시키려고 할 때, except 문의 맨 뒤에 raise 하나만 붙임으로써 간단하게 발생한 예외를 재발생 시킬 수 있다.
 
raise 문은 예외를 다음 tryexcept 문으로 “올린다”는 점에서 그 이름이 raise 문이라고 이해하면 간단하다.
raise 문은 프로그래머가 특정한 상황에서 특정한 예외를 발생 시킬 수 있게 해주어 프로그램을 더욱 정교하게 만들 수 있게 해준다.
 

4. Exception Chaining

4.1 except 문 안의 예외

try: open("non_existent_file.txt") except OSError: raise RuntimeError("unable to handle error")
page icon
Traceback (most recent call last): File "path/to/folder/code.py", line 2, in <module> open("non_existent_file.txt") ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory: 'non_existent_file.txt' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "path/to/folder/code.py", line 4, in <module> raise RuntimeError("unable to handle error") RuntimeError: unable to handle error
예외를 처리할 때, except 문으로 예외를 처리하려고 해도, 그 except 문 안에서 또 다른 예외가 발생할 수도 있다.
except 문 안에서 예외가 발생한 경우, 이 예외에는 추가로 예외를 처리하지 못했다는 메세지가 붙고, 다시 tryexcept 문 밖으로 나와 일반적인 예외처럼 행동한다.
 

4.2 from

try: open("non_existent_file.txt") except OSError as exc: raise RuntimeError("unable to handle error") from exc
page icon
Traceback (most recent call last): File "path/to/folder/code.py", line 2, in <module> open("non_existent_file.txt") ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory: 'non_existent_file.txt' The above exception was the direct cause of the following exception: Traceback (most recent call last): File "path/to/folder/code.py", line 4, in <module> raise RuntimeError("unable to handle error") from exc RuntimeError: unable to handle error
만약 raise 문을 통해 except 문 안에서 다시 예외를 발생 시키려는 경우, 발생 시키려는 예외가 다른 예외로부터 직접적으로 발생한 예외라는 관계를 나타내고 싶을 때 from 문을 사용할 수 있다.
from 문은 raise 문 뒤에 붙어, 예외 인스턴스를 받는다.
raise 문에 from 문을 붙임으로써 raise 로 발생 시키는 예외가 from 의 예외 인스턴스로부터 생겨난 예외라는 것을 나타낼 수 있다.
 
try: open("non_existent_file.txt") except OSError: raise Exception("another exception") from None
page icon
Traceback (most recent call last): File "path/to/folder/code.py", line 4, in <module> raise Exception("another exception") from None Exception: another exception
만약 raise 문으로 예외를 발생 시키려 하지만 해당 예외가 다른 예외와 관련이 전혀 없을 경우, from None 을 사용하여 이를 나타낼 수 있다.
from None 을 사용하면 다른 예외와 연결되지 않은 채 예외를 발생 시킬 수 있다.
 

5. 사용자 정의 예외

class MyError(Exception): pass try: raise MyError('An error occurred') except MyError as e: print(f'Caught an error: {e}')
page icon
Caught MyError: An error occurred
프로그래머는 임의로 사용자 정의 예외를 정의할 수 있다.
사용자 정의 예외는 Exception 클래스를 상속하는 클래스를 정의함으로써 만들 수 있다.
예외의 클래스명은 “Error”로 끝나도록 짓는 것이 일반적이다.
 

6. finally

6.1 finally 문 기본

try: int(input('숫자를 입력해주세요: ')) except ValueError: print('유효한 숫자가 아닙니다.') finally: print('Hello, World!')
page icon
숫자를 입력해주세요: asdf 유효한 숫자가 아닙니다. Hello, World!
page icon
숫자를 입력해주세요: 3 Hello, World!
try 문에 finally 문이 붙을 경우, finally 의 코드는 전체 try 문의 실행이 끝날 때 마지막으로 실행된다.
이는 try문에서의 예외 발생 여부와 관계없이 try 문이 종료되기 전에 마지막으로 무조건 실행된다.
일반적으로 finally 문은 외부 리소스를 release하는 것과 같이, try 문이 정상적으로 실행되던 그렇지 않던 무조건 마지막으로 처리해야 하는 상황에 사용된다.
 

6.2 예외 재발생

try: print('try clause') raise Exception('An error occurred') finally: print('finally clause')
page icon
try clause finally clause Traceback (most recent call last): File "path/to/folder/code.py", line 3, in <module> raise Exception('An error occurred') Exception: An error occurred
만약 finally 가 붙은 try 문 안에서 예외가 발생하였지만 except 문으로 처리하지 못하였다면, 해당 예외를 올리기 전에 먼저 finally 문을 실행한 다음 그 예외를 다시 발생 시킨다.
try: print('try clause') int(input('숫자를 입력해주세요: ')) except ValueError: print('except clause') raise Exception('exception on "except"') else: print('else clause') raise Exception('exception on "else"') finally: print('finally clause')
page icon
try clause 숫자를 입력해주세요: asdf except clause finally clause Traceback (most recent call last): File "path/to/folder/code.py", line 3, in <module> int(input('숫자를 입력해주세요: ')) ~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ValueError: invalid literal for int() with base 10: 'asdf' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "path/to/folder/code.py", line 6, in <module> raise Exception('exception on except') Exception: exception on "except"
page icon
try clause 숫자를 입력해주세요: 3 else clause finally clause Traceback (most recent call last): File "path/to/folder/code.py", line 9, in <module> raise Exception('exception on else') Exception: exception on "else"
만약 except 문이나 else 문 안에서 예외가 발생한 경우에도, 마찬가지로 finally 문을 먼저 실행한 후에 예외를 다시 발생 시킨다.
 

6.3 break , continue , return

def input_number(): x = 0 try: x = int(input('숫자를 입력해주세요: ')) finally: return x value = input_number() print(f'입력한 숫자는 {value}입니다.')
page icon
숫자를 입력해주세요: 3 입력한 숫자는 3입니다.
page icon
숫자를 입력해주세요: asdf 입력한 숫자는 0입니다.
만약 finally 문 안에서 break , continue , return 문이 실행된다면, finally 문 이후에 예외를 재발생 시키지 않는다.
위의 예시에서 “asdf”를 입력했을 경우에 원래는 ValueError 가 raise되었어야 했지만, return 문이 finally 문 안에서 실행되었기 때문에 예외가 재발생 되지 않았다.
 
def input_number(): x = 0 try: x = int(input('숫자를 입력해주세요: ')) print('입력에 성공했습니다.') return x finally: print('finally clause') value = input_number() print(f'입력한 숫자는 {value}입니다.')
page icon
숫자를 입력해주세요: 3 입력에 성공했습니다. finally clause 입력한 숫자는 3입니다.
page icon
숫자를 입력해주세요: asdf finally clause Traceback (most recent call last): File "path/to/folder/code.py", line 10, in <module> value = input_number() File "path/to/folder/code.py", line 4, in input_number x = int(input('숫자를 입력해주세요: ')) ~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ValueError: invalid literal for int() with base 10: 'asdf'
이와 다르게 try 문 안에서 break , continue , return 문이 실행된다면, 그것을 실행하기 전에 finally 문을 먼저 실행하고 break , continue , return 문을 실행한다.
try 문 안에서 break , continue , return 문이 사용되었을 경우에는 원래대로 ValueError 를 재발생 시킨다.
 
def bool_return(): try: return True finally: return False bool_return()
page icon
False
만약 finally 문에 return 문이 있을 경우, 그 함수는 무조건 finally 문의 return 값을 반환한다.
이는 try 문 안에 return 문이 있더라도 그 return 문을 실행하기 전에 finally 문을 먼저 실행하기 때문에 일어나는 일이다.
 

7. with

with open("myfile.txt") as f: for line in f: print(line, end="")
with 문은 오브젝트의 clean-up을 간단하게 만들어주는 문법으로, tryexceptfinally 구조를 압축한 것이다.
만약 오브젝트에 __enter__ , __exit__ 메소드가 구현되어 있으면 with 문을 사용할 수 있다.
그러면 with 문은 코드를 먼저 실행하기 전에 __enter__ 메소드를 실행하고 finally 문으로 __exit__ 메소드를 실행한다.

8. 다수 예외 처리

8.1 ExceptionGroup

def multiple_exceptions(): excs = [ OSError('error1'), ValueError('error2'), TypeError('error3'), ] raise ExceptionGroup('Multiple exceptions occurred', excs) multiple_exceptions()
page icon
+ Exception Group Traceback (most recent call last): | File "path/to/folder/code.py", line 9, in <module> | multiple_exceptions() | ~~~~~~~~~~~~~~~~~~~^^ | File "path/to/folder/code.py", line 7, in multiple_exceptions | raise ExceptionGroup('Multiple exceptions occurred', excs) | ExceptionGroup: Multiple exceptions occurred (3 sub-exceptions) +-+---------------- 1 ---------------- | OSError: error1 +---------------- 2 ---------------- | ValueError: error2 +---------------- 3 ---------------- | TypeError: error3 +------------------------------------
여러 개의 예외를 raise하고 싶을 때,ExceptionGroup 클래스를 통해 여러 개의 예외들을 한꺼번에 raise할 수 있다.
이는 병렬 처리를 하던 중에 여러 개의 예외가 일어난 경우 등, 관련이 별로 없는 예외들에 대해 쓰일 수 있다.
 

8.2 except*

def multiple_exceptions(): excs = [ OSError('error1'), ValueError('error2'), TypeError('error3'), ] raise ExceptionGroup('Multiple exceptions occurred', excs) try: multiple_exceptions() except* OSError as e: print(f'Caught OSError: {e.args}') except* ValueError as e: print(f'Caught ValueError: {e.args}') except* TypeError as e: print(f'Caught TypeError: {e.args}')
page icon
Caught OSError: ('Multiple exceptions occurred', [OSError('error1')]) Caught ValueError: ('Multiple exceptions occurred', [ValueError('error2')]) Caught TypeError: ('Multiple exceptions occurred', [TypeError('error3')])
ExceptionGroup 을 통해 한번에 여러 개의 예외들을 raise했을 경우, except 문이 아닌 except* 문을 통해 ExceptionGroup 의 세부 예외들을 매칭할 수 있다.
 

9. add_note 함수

try: raise TypeError('bad type') except Exception as e: e.add_note('Add some information') e.add_note('Add some more information') raise
page icon
Traceback (most recent call last): File "path/to/folder/code.py", line 2, in <module> raise TypeError('bad type') TypeError: bad type Add some information Add some more information
raise 를 통해 except 에서 받은 예외를 재발생 시킬 때, add_note(note) 함수를 통해 에러 메세지에 추가 정보를 노트를 통해 첨부할 수 있다.
add_note 함수는 Exception 클래스의 메소드로, 예외 인스턴스의 __notes__ 에 노트를 추가해준다.
__notes__ 속성은 직접적으로 드러나지는 않지만 예외를 Traceback할 경우에만 드러난다.