[Python] 예외
예외(Exception)란?
프로그래밍을 하면서 빈번하게 만나는 것이 바로 예외(Exception)입니다. 예외는 프로그램 실행 중 발생할 수 있는 비정상적인 조건이나 오류 상황을 의미합니다. 이러한 예외 상황에 대처하는 메커니즘이 바로 예외 처리(Exception Handling)입니다. 이 섹션에서는 Python에서 예외의 개념에 대해 상세히 살펴보고, 예외가 발생하는 일반적인 원인들과 예외 처리가 왜 중요한지에 대해 탐구해 보겠습니다.
예외의 정의
예외는 프로그램의 정상적인 실행 흐름을 방해하는 이벤트로 정의됩니다. 다시 말해, 예외는 프로그램의 코드에 의해 발생하거나 프로그램 외부의 영향으로 인해 발생할 수 있는 모든 종류의 오류나 문제를 말합니다. 예외는 보통 두 가지 범주로 나뉩니다:
- 시스템 예외: 프로그램 외부에서 발생하는 예외로, 파일을 찾을 수 없거나, 네트워크 연결이 끊어지는 상황과 같은 환경적 요인에 의해 발생합니다.
- 프로그램 예외: 프로그래머가 직접 관리할 수 있는 예외로, 배열의 인덱스를 잘못 접근하거나, 잘못된 타입의 객체를 사용하는 등의 상황에서 발생합니다.
예외 발생의 일반적인 원인
예외가 발생하는 일반적인 원인들에는 여러 가지가 있습니다. 가장 일반적인 몇 가지 사례를 살펴봅시다:
- 사용자의 잘못된 입력
- 파일 시스템의 문제 (예: 읽으려는 파일이 존재하지 않음)
- 네트워크 연결 오류
- 0으로 나누기 시도와 같은 잘못된 연산
- 존재하지 않는 변수나 함수의 사용 시도
예외 처리의 중요성
프로그램에서 예외 처리를 적절히 수행하는 것은 매우 중요합니다. 정확히 해결되지 않은 예외는 프로그램의 예기치 않은 종료를 가져올 수 있고, 사용자에게 혼란을 주거나 데이터 손실을 일으킬 수 있습니다. 예외 처리를 통해 프로그램의 안정성과 신뢰성을 향상시킬 수 있으며, 다음과 같은 이점을 얻을 수 있습니다:
- 오류 진단과 처리가 용이해집니다.
- 프로그램의 비정상적 종료를 방지하고, 사용자에게 유용한 오류 메시지를 제공할 수 있습니다.
- 프로그램의 유지 보수와 디버깅이 보다 수월해집니다.
- 사용자 데이터의 안전을 보장하고 프로그램의 안정적인 동작을 유지할 수 있습니다.
예를 들어, 사용자가 파일을 열려고 할 때 해당 파일이 존재하지 않는 상황을 보겠습니다. 여기서 예외 처리를 사용하지 않으면 프로그램은 비정상적으로 종료될 것입니다. 하지만 예외 처리를 사용하면, 이러한 상황을 감지하고 사용자에게 파일이 존재하지 않는다는 메시지를 표시할 수 있으며, 프로그램은 계속해서 실행될 수 있습니다.
try:
with open('example.txt', 'r') as file:
data = file.read()
except FileNotFoundError:
print("파일을 찾을 수 없습니다.")
위 코드 예시는 FileNotFoundError
예외가 발생할 경우 이를 처리하는 방법을 보여줍니다. 이는 사용자에게 친숙한 오류 메시지를 제공하고, 프로그램이 예외에 의해 종료되는 것을 방지합니다.
try 블록 사용하기
Python에서 예외 처리는 일반적으로 try
, except
문을 사용하여 수행됩니다. 이 구조를 통해 코드 실행 중에 발생할 수 있는 오류 또는 예외(exception)를 우아하게 처리할 수 있습니다. 특히, try
블록 내에서 코드를 실행할 때 예외가 발생하면 프로그램이 갑자기 중단되는 대신 잡아서 적절하게 대응할 수 있는 기회를 얻게 됩니다.
try 블록의 기본 구조
try
블록의 기본 구조는 다음과 같습니다:
try:
# 실행하고자 하는 코드
except 예외타입:
# 예외 발생 시 실행할 코드
여기서 try
블록 안에는 예외가 발생할 가능성이 있는 코드가 위치합니다. 만약 try
블록 내의 코드 실행 중에 예외가 발생하면, 해당 예외 타입에 맞는 except
블록이 실행됩니다.
try 블록 안에서 코드 실행의 흐름
try
블록을 사용하여 코드를 작성하고 실행할 때, 프로그램의 흐름은 다음과 같습니다:
try
블록 내의 코드가 실행됩니다.- 코드 실행 중에 예외가 발생하지 않으면,
try
블록 내의 모든 코드가 정상적으로 실행된 후try-except
구조를 벗어납니다. 이후except
블록은 무시됩니다. - 만약
try
블록 실행 중에 예외가 발생하면, 해당 예외 타입과 일치하는except
블록으로 제어가 전달됩니다.except
블록 내의 코드가 실행된 후,try-except
구조를 벗어납니다. - 여러
except
블록이 있을 경우, 발생한 예외 타입과 일치하는 첫 번째except
블록만 실행됩니다. - 선택적으로
else
및finally
블록을 추가하여 더 복잡한 예외 처리 로직을 구성할 수 있습니다.
예시
다음 예제는 try
블록의 사용법을 보여줍니다:
try:
# 0으로 나누기 시도
result = 10 / 0
except ZeroDivisionError:
# ZeroDivisionError 예외 발생 시 실행
print("0으로 나눌 수 없습니다.")
이 예제에서는 try
블록 안에서 0으로 나누는 연산을 시도합니다. Python에서는 이러한 연산을 수행하려 하면 ZeroDivisionError
예외가 발생합니다. 따라서 try
블록 내의 코드가 실행되다가 예외가 발생하면, 직접적으로 except ZeroDivisionError:
블록으로 이동하여 "0으로 나눌 수 없습니다."라는 메시지를 출력합니다.
위의 설명과 예시를 통해 try
블록의 기본 구조와 코드 실행의 흐름을 이해할 수 있습니다. try
블록을 적절하게 사용함으로써 보다 안정적이고 오류에 대응할 수 있는 코드를 작성할 수 있습니다.
except 블록 사용하기
예외 처리는 프로그램에서 발생할 수 있는 예외적 상황, 즉 오류나 예기치 않은 이벤트를 적절히 처리하기 위해 사용됩니다. Python에서는 try
, except
구문을 이용해 이러한 예외를 처리할 수 있습니다. 이 섹션에서는 except
블록의 다양한 사용법에 대해 알아볼 것입니다.
except 블록의 기본 구조
기본적인 except
블록의 구조는 다음과 같습니다:
try:
# 예외가 발생할 수 있는 코드
except 예외타입:
# 예외가 발생했을 때 실행할 코드
try
블록 내에서 코드를 실행하다가 예외가 발생하면, 해당 예외타입과 일치하는 except
블록이 실행됩니다.
예시:
try:
result = 10 / 0
except ZeroDivisionError:
print("0으로 나눌 수 없습니다.")
이 예시에서는 0으로 나누는 연산이 try
블록 내에서 시도되며, 이는 ZeroDivisionError
예외를 발생시킵니다. 해당 예외와 일치하는 except
블록이 실행되어 사용자에게 오류 메시지를 출력합니다.
하나의 except 블록으로 여러 예외 처리하기
하나의 except
블록으로 여러 종류의 예외를 처리할 수 있습니다. 이때 예외 타입들은 튜플로 묶어 지정합니다.
예시:
try:
# 예외가 발생할 수 있는 코드
except (ZeroDivisionError, ValueError):
# ZeroDivisionError나 ValueError가 발생했을 때 실행할 코드
이 구조를 사용하면 하나의 except
블록에서 여러 타입의 예외를 처리할 수 있습니다.
특정 예외만 처리하기
특정 예외를 명시함으로써, 그 예외가 발생했을 때만 특정 작업을 수행하도록 할 수 있습니다. 이는 프로그램의 정확성을 높이는데 유용합니다.
예외 객체에 접근하기
except
구문에서 예외 객체에 접근하여, 발생한 예외에 대한 더 많은 정보를 얻을 수 있습니다. 이를 위해 다음과 같이 작성할 수 있습니다:
try:
# 예외가 발생할 수 있는 코드
except 예외타입 as 예외변수명:
# 예외변수를 사용한 코드
예시:
try:
result = 10 / 0
except ZeroDivisionError as e:
print("에러 발생:", e)
이 코드는 0으로 나누려고 할 때 발생하는 ZeroDivisionError
를 포착하고, 그 예외 객체를 변수 e
를 통해 받습니다. 그리고 print
함수를 이용해 에러 메시지를 출력합니다. 이 방식은 발생한 예외에 대한 상세한 정보를 제공하여, 디버깅에 매우 유용합니다.
else 블록
else
블록은 예외 처리 구조에서 흔히 간과되는 부분입니다. 그러나 적절히 사용될 때, 코드의 가독성과 구조화를 크게 향상시킬 수 있습니다. else
블록은 try
블록이 예외 없이 성공적으로 실행된 후 실행되는 코드 블록을 담습니다. 다시 말해, try
블록 내의 코드가 예외를 발생시키지 않았을 때만 실행됩니다.
else 블록의 위치와 목적
try
블록 다음에 바로 except
블록이 오고, 모든 except
블록이 끝난 후 else
블록이 옵니다. else
블록의 목적은 예외가 발생하지 않고 try
블록이 성공적으로 실행됐을 때만 실행되어야 하는 코드를 분리하는 것입니다. 이렇게 함으로써, 코드의 가독성을 향상시키고, try
블록 내의 코드를 최소화하여 예외 발생 가능성을 줄일 수 있습니다.
try 블록에서 예외가 발생하지 않았을 때만 실행되는 코드 작성 방법
try
블록에서 예외가 발생하지 않았을 때만 실행되어야 하는 로직을 else
블록에 작성합니다. 이는 try
블록을 통해 성공적으로 실행된 코드의 결과를 기반으로 추가 작업을 수행할 경우 유용합니다.
else 블록 사용 예제
파일 읽기 작업 예제
파일을 읽는 작업에서 try
블록을 사용하여 파일 열기를 시도하고, 해당 파일이 없어 예외가 발생하는 경우를 except
로 처리합니다. else
블록에서는 파일을 성공적으로 읽은 경우에 실행될 코드를 작성합니다.
try:
with open("example.txt", "r") as file:
data = file.read()
except FileNotFoundError:
print("파일을 찾을 수 없습니다.")
else:
print("파일의 내용은 다음과 같습니다:")
print(data)
사용자 입력 처리 예제
사용자 입력을 처리하는 예에서 try
블록을 사용하여 입력 값을 정수로 변환합니다. 입력 값이 정수로 변환되지 않는 경우 ValueError
예외를 처리합니다. 변환에 성공하면 else
블록에서 추가 작업을 수행합니다.
try:
num = input("정수를 입력하세요: ")
num_int = int(num)
except ValueError:
print("정수가 아닙니다.")
else:
print(f"입력하신 정수는 {num_int}입니다.")
이러한 예들에서 볼 수 있듯이, else
블록을 사용함으로써, 우리는 예외가 발생하지 않았을 때의 동작을 더 명확히 할 수 있으며 코드의 가독성을 개선할 수 있습니다.
finally 블록
finally 블록의 위치와 목적
finally
블록은 Python에서 예외(exception) 처리 구조인 try
, except
절의 마지막 부분에 위치합니다. 그 목적은 예외 발생 여부와 상관없이 프로그램이 반드시 실행해야 하는 "정리" 코드를 담는 것입니다. 주로 사용되는 케이스는 열린 파일의 닫기, 네트워크 연결 해제, 임시 리소스의 정리 등이 있습니다. 즉, 어떤 상황에서든지 마무리 동작을 보장해주는 구문입니다.
예외 발생 여부와 상관없이 실행되어야 하는 정리 코드 작성 방법
finally
블록 안에는 예외가 발생하더라도 반드시 실행되어야 할 코드를 작성합니다. 이는 주로 외부 자원을 사용하는 경우 그 자원을 안전하게 해제하거나 반환해야 할 때 필요합니다. 예외 처리를 함으로써 프로그램의 안정성과 메모리 누수 방지 등을 기대할 수 있습니다.
try:
# 예외가 발생할 가능성이 있는 코드
except Exception as e:
# 예외가 발생했을 때 처리할 코드
finally:
# 예외 발생 여부와 상관없이 반드시 실행되어야 하는 정리 코드
finally 블록 사용 예제
파일 닫기 작업 예제
파일 작업은 예외가 발생할 수 있는 대표적인 영역입니다. 파일을 열고 작업을 진행하다가 예외가 발생하더라도, 반드시 파일을 닫아 리소스를 해제해야 합니다.
try:
f = open("example.txt", "w")
f.write("Hello, Python!")
except Exception as e:
print(f"An error occurred: {e}")
finally:
f.close()
print("File has been closed.")
위 코드에서는 파일을 열어 "Hello, Python!" 문자열을 쓰고 있습니다. 예외가 발생하든 발생하지 않든 finally
블록에 있는 f.close()
는 항상 실행됩니다.
네트워크 연결 해제 작업 예제
네트워크 연결 역시 예외가 발생하기 쉬운 영역입니다. 예를 들어, 원격 서버에 데이터를 요청하고, 그 응답을 처리하는 중 예외가 발생할 수 있습니다. 하지만 네트워크 연결은 반드시 해제되어야 합니다.
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(("example.com", 80))
# 네트워크 작업 수행, 예외가 발생할 수 있는 부분
except Exception as e:
print(f"An error occurred: {e}")
finally:
s.close()
print("Socket has been closed.")
위 예시는 소켓을 통해 원격 서버와의 연결을 시도하고, 어떠한 예외가 발생하든 finally
블록을 이용해 소켓을 안전하게 닫고 프로그램의 정상적인 종료를 보장합니다.
예외의 전파
예외 전파(exception propagation)는 일련의 함수 호출이 이루어지는 환경에서 다루어진다. 특정 함수 내에서 발생한 예외가 그 함수 내에서 처리되지 않을 경우, 호출한 함수로 예외가 전파됩니다. 이러한 방식으로 예외가 호출 체인을 거슬러 올라가다가 적절히 처리될 수 있습니다. 예외를 처리하는 방법은 크게 두 가지가 있으며, 예외 체인은 예외 처리의 맥락을 명확히 하는 데 도움을 줍니다.
함수에서 예외 처리
예외를 외부로 전파하는 방법
함수 내에서 발생한 예외를 내부에서 처리하지 않고 명시적으로 처리를 원하는 경우, 단순하게 raise
를 활용해 예외를 던질 수 있습니다. 이렇게 하면 함수를 호출한 상위 코드 블록에서 예외를 처리할 기회를 제공합니다.
def calculate_division(x, y):
if y == 0:
raise ValueError("y 값이 0이면 분모가 될 수 없습니다.")
return x / y
try:
result = calculate_division(10, 0)
except ValueError as e:
print(e)
내부에서 예외 처리 후 다시 예외 발생시키기
때로는 함수 내부에서 예외를 처리한 후, 다른 예외를 발생시켜 호출자에게 더 의미 있는 정보를 제공해야 할 필요가 있습니다. 이는 raise
구문을 이용해 새로운 예외를 발생시키는 것으로 처리할 수 있습니다.
def calculate_division(x, y):
try:
result = x / y
except ZeroDivisionError:
print("분모로 0이 올 수 없습니다.")
raise ValueError("유효하지 않은 입력값") from None
return result
try:
result = calculate_division(10, 0)
except ValueError as e:
print(e)
예외 체인 (Exception Chaining)
예외 발생의 원인을 연결하는 방법
Python 3에서는 예외 체인을 사용하여 예외의 원인을 명확히 할 수 있습니다. raise new_exception from original_exception
구문을 사용하여 새로운 예외를 발생시키며, 동시에 원본 예외의 정보를 유지할 수 있습니다. 이는 디버깅에 매우 유용합니다.
try:
# 일부 연산을 수행, 여기서 예외가 발생한다고 가정합니다.
except Exception as e:
raise RuntimeError("새로운 예외 처리") from e
raise from 구문의 사용법
raise from
구문은 두 예외 사이에 명시적인 연관성을 만들 때 사용됩니다. 이는 중첩된 예외를 처리할 때 유용하며, 예외 처리의 투명성을 증가시킵니다.
예를 들어, 파일 처리 중 발생한 예외를 더 상위 레벨의 예외로 변환할 때 raise from
구문을 사용할 수 있습니다.
def process_file(path):
try:
# 파일 열기 시도
except FileNotFoundError as e:
raise RuntimeError(f"{path} 파일 처리 중 문제 발생") from e
이 방법으로 예외의 원인과 효과를 명확히 연결하며, 예외 처리를 더 직관적으로 이해할 수 있습니다. 예외 체인을 사용하면 개발자는 오류의 근본 원인을 효과적으로 추적하고 문제를 신속하게 해결할 수 있습니다.