try: open('없는파일.txt') → except: print('파일 없어요!') → 프로그램 계속 실행! 😊
에러 나도 튕기지 않는 프로그램 만들기! 파일 없음, 0으로 나누기, 네트워크 끊김… 모든 에러를 우아하게 처리합니다!
(30-40분 완독 ⭐⭐⭐)
🎯 오늘의 학습 목표
📚 사전 지식
🎯 학습 목표 1: 예외와 에러의 개념 이해하기
1.1 예외의 개념
예외(Exception): 프로그램 실행 중 발생하는 오류
1
2
| # ❌ 예외 발생 예제
result = 10 / 0 # ZeroDivisionError!
|
예외가 발생하면:
1.2 예외 vs 구문 오류
1
2
3
4
5
6
7
| # 구문 오류 (Syntax Error) - 실행 전에 발견
# if True
# print("Hello") # SyntaxError: invalid syntax
# 예외 (Exception) - 실행 중에 발생
numbers = [1, 2, 3]
print(numbers[10]) # IndexError: list index out of range
|
🎯 학습 목표 2: try-except로 예외 처리하기 기본
2.1 기본 구조
1
2
3
4
5
6
7
8
| try:
# 예외가 발생할 수 있는 코드
result = 10 / 0
except:
# 예외 발생 시 실행
print("오류가 발생했습니다!")
print("프로그램 계속 실행")
|
2.2 특정 예외 처리
1
2
3
4
5
6
7
8
9
10
11
12
| try:
num = int(input("숫자 입력: "))
result = 10 / num
print(f"결과: {result}")
except ValueError:
print("❌ 숫자가 아닙니다!")
except ZeroDivisionError:
print("❌ 0으로 나눌 수 없습니다!")
print("프로그램 종료")
|
2.3 여러 예외 한 번에
1
2
3
4
| try:
# ...
except (ValueError, TypeError, ZeroDivisionError):
print("오류 발생!")
|
🎯 학습 목표 3: 다양한 예외 타입 다루기
3.1 일반적인 예외들
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # ZeroDivisionError - 0으로 나누기
result = 10 / 0
# ValueError - 부적절한 값
num = int("abc")
# TypeError - 타입 불일치
result = "5" + 3
# IndexError - 인덱스 범위 초과
numbers = [1, 2, 3]
print(numbers[10])
# KeyError - 존재하지 않는 키
data = {"name": "Alice"}
print(data["age"])
# FileNotFoundError - 파일 없음
with open("없는파일.txt") as f:
content = f.read()
# AttributeError - 속성 없음
text = "Hello"
text.append("!") # 문자열엔 append 없음
|
3.2 예외 계층 구조
graph TD
A[BaseException] --> B[Exception]
B --> C[ValueError]
B --> D[TypeError]
B --> E[KeyError]
B --> F[FileNotFoundError]
B --> G[ZeroDivisionError]
Exception 계층 이해하기:
Exception을 잡으면 모든 하위 예외가 잡힙니다 - 구체적인 예외를 먼저, 일반적인 예외를 나중에 처리하세요
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # ✅ 좋은 예 (구체적 → 일반적)
try:
result = int(data)
except ValueError:
print("숫자 변환 실패")
except Exception as e:
print(f"예상치 못한 오류: {e}")
# ❌ 나쁜 예 (일반적 예외가 먼저)
try:
result = int(data)
except Exception: # ValueError까지 여기서 잡힘!
print("오류 발생")
except ValueError: # 이 코드는 절대 실행되지 않음
print("숫자 변환 실패")
|
🎯 학습 목표 4: 예외 정보 다루기
4.1 예외 객체 활용
1
2
3
4
5
6
7
8
9
10
11
| try:
num = int("abc")
except ValueError as e:
print(f"오류 타입: {type(e).__name__}")
print(f"오류 메시지: {e}")
print(f"오류 인자: {e.args}")
# 출력:
# 오류 타입: ValueError
# 오류 메시지: invalid literal for int() with base 10: 'abc'
# 오류 인자: ("invalid literal for int() with base 10: 'abc'",)
|
4.2 상세한 오류 정보
1
2
3
4
5
6
7
8
9
10
| import traceback
try:
result = 10 / 0
except Exception as e:
print("오류 발생!")
print(f"타입: {type(e).__name__}")
print(f"메시지: {e}")
print("\n전체 스택 트레이스:")
traceback.print_exc()
|
🎯 학습 목표 5: finally와 else 절 사용하기
5.1 else절
else절은 언제 실행되나요?
- try 블록이 예외 없이 정상 완료되었을 때만 실행됩니다
- 예외가 발생하면 else절은 건너뛰고 except절로 갑니다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| try:
num = int(input("숫자 입력: "))
result = 10 / num
except ValueError:
print("❌ 숫자가 아닙니다")
except ZeroDivisionError:
print("❌ 0으로 나눌 수 없습니다")
else:
# 예외가 발생하지 않았을 때만 실행
print(f"✅ 결과: {result}")
print("프로그램 종료")
|
5.2 finally절
finally절의 특징:
- 예외 발생 여부와 관계없이 항상 실행됩니다
- 주로 파일 닫기, 연결 해제 등 리소스 정리에 사용합니다
💡 더 나은 패턴 (권장):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| f = None # 파일 변수를 먼저 초기화
try:
f = open("data.txt", "r")
content = f.read()
print(content)
except FileNotFoundError:
print("❌ 파일을 찾을 수 없습니다")
finally:
# f가 열렸을 때만 닫기
if f:
f.close()
print("파일 닫힘")
|
🔍 왜 이렇게 하나요?
open()에서 예외가 발생하면 f가 정의되지 않을 수 있습니다 if f: 조건으로 파일이 실제로 열렸는지 확인합니다
5.3 전체 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| try:
# 예외가 발생할 수 있는 코드
pass
except SomeException:
# 예외 처리
pass
else:
# 예외가 없을 때
pass
finally:
# 항상 실행
pass
|
5.4 실행 흐름 이해하기
graph TD
A[try 블록 시작] --> B{예외 발생?}
B -->|예외 없음| C[else 블록 실행]
B -->|예외 발생| D[except 블록 실행]
C --> E[finally 블록 실행]
D --> E
E --> F[프로그램 계속]
흐름 정리:
- try 실행 → 예외 없으면 else → finally → 계속
- try 실행 → 예외 발생 → except → finally → 계속
- finally는 항상 실행됩니다
🎯 학습 목표 6: 실전 예외 처리 패턴
패턴 1: 재시도
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| def get_number_with_retry(max_attempts=3):
"""사용자 입력 재시도"""
for attempt in range(max_attempts):
try:
num = int(input("숫자 입력: "))
return num
except ValueError:
remaining = max_attempts - attempt - 1
if remaining > 0:
print(f"❌ 숫자가 아닙니다. ({remaining}번 남음)")
else:
print("❌ 시도 횟수 초과")
raise
# 사용
# num = get_number_with_retry()
|
패턴 2: 안전한 파일 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| def safe_read_file(filename):
"""안전한 파일 읽기"""
try:
with open(filename, 'r', encoding='utf-8') as f:
return f.read()
except FileNotFoundError:
print(f"⚠️ 파일 없음: {filename}")
return None
except PermissionError:
print(f"⚠️ 권한 없음: {filename}")
return None
except Exception as e:
print(f"⚠️ 예상치 못한 오류: {e}")
return None
# 사용
content = safe_read_file("data.txt")
if content:
print(content)
|
패턴 3: 기본값 제공
1
2
3
4
5
6
7
8
9
10
| def get_config_value(config, key, default=None):
"""설정 값 안전하게 가져오기"""
try:
return config[key]
except KeyError:
return default
# 사용
config = {"debug": True}
timeout = get_config_value(config, "timeout", default=30)
|
패턴 4: 타입 변환
1
2
3
4
5
6
7
8
9
10
11
| def safe_int(value, default=0):
"""안전한 정수 변환"""
try:
return int(value)
except (ValueError, TypeError):
return default
# 사용
age = safe_int("25") # 25
age = safe_int("abc") # 0
age = safe_int(None) # 0
|
패턴 5: raise - 예외 발생시키기
언제 사용하나요?
- 함수에서 잘못된 입력을 받았을 때
- 비즈니스 로직 규칙 위반 시
- 예외를 기록한 후 다시 던질 때
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| def divide(a, b):
"""나눗셈 (음수 불가)"""
if b == 0:
raise ZeroDivisionError("0으로 나눌 수 없습니다")
if a < 0 or b < 0:
raise ValueError("음수는 사용할 수 없습니다")
return a / b
# 사용
try:
result = divide(10, -2)
except ValueError as e:
print(f"❌ {e}")
|
예외 재발생 (raise)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| def process_data(data):
try:
# 데이터 처리
result = int(data) * 2
except ValueError as e:
print("⚠️ 로그: 데이터 처리 실패")
raise # 예외를 다시 발생시킴
# 사용
try:
process_data("abc")
except ValueError:
print("❌ 외부에서 처리")
|
패턴 6: 종합 실전 예제
예제 1: 안전한 JSON 파일 로드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| import json
def load_json_safe(filename, default=None):
"""안전한 JSON 로드"""
try:
with open(filename, 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
print(f"⚠️ 파일 없음: {filename}")
return default or {}
except json.JSONDecodeError as e:
print(f"⚠️ JSON 파싱 오류: {e}")
return default or {}
except Exception as e:
print(f"⚠️ 예상치 못한 오류: {e}")
return default or {}
# 사용
config = load_json_safe('config.json', default={'debug': False})
print(config)
|
예제 2: 계산기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| def calculator():
"""간단한 계산기 (예외 처리)"""
while True:
try:
expr = input("계산식 입력 (종료: q): ")
if expr.lower() == 'q':
break
# ⚠️ 주의: eval()은 보안상 위험할 수 있습니다
# 실제 프로덕션에서는 ast.literal_eval() 사용 권장
result = eval(expr)
print(f"결과: {result}")
except ZeroDivisionError:
print("❌ 0으로 나눌 수 없습니다")
except (ValueError, SyntaxError, NameError):
print("❌ 잘못된 수식입니다")
except KeyboardInterrupt:
print("\n프로그램 중단")
break
except Exception as e:
print(f"❌ 오류: {e}")
# calculator()
|
예제 3: 데이터 검증
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| def validate_user_data(data):
"""사용자 데이터 검증"""
errors = []
# 이름 검증
try:
name = data['name']
if not isinstance(name, str) or len(name) < 2:
errors.append("이름은 2자 이상이어야 합니다")
except KeyError:
errors.append("이름은 필수입니다")
# 나이 검증
try:
age = int(data['age'])
if age < 0 or age > 150:
errors.append("나이는 0-150 사이여야 합니다")
except KeyError:
errors.append("나이는 필수입니다")
except ValueError:
errors.append("나이는 숫자여야 합니다")
# 이메일 검증
try:
email = data['email']
if '@' not in email:
errors.append("올바른 이메일 형식이 아닙니다")
except KeyError:
pass # 이메일은 선택사항
if errors:
raise ValueError(f"검증 실패:\n" + "\n".join(f" • {e}" for e in errors))
# 사용
user1 = {"name": "Alice", "age": "25", "email": "alice@example.com"}
user2 = {"name": "B", "age": "-5"}
try:
validate_user_data(user1)
print("✅ user1 검증 성공")
except ValueError as e:
print(e)
try:
validate_user_data(user2)
except ValueError as e:
print(f"❌ user2 검증 실패:\n{e}")
|
💡 실전 팁 & 주의사항
✅ 좋은 예외 처리 습관
- 구체적인 예외 처리
1
2
3
4
5
6
7
8
9
10
11
| # ❌ 나쁜 예
try:
result = int(data)
except: # KeyboardInterrupt, SystemExit까지 잡아버림!
pass # 심지어 무시까지!
# ✅ 좋은 예
try:
result = int(data)
except ValueError: # 특정 예외만
result = 0
|
왜 나쁜가요?
- 빈
except:는 모든 예외를 잡아버립니다 (Ctrl+C도 잡힘!) - 디버깅이 매우 어려워집니다
- 예상치 못한 오류를 숨길 수 있습니다
- 예외 무시하지 않기
1
2
3
4
5
6
7
8
9
10
11
| # ❌ 나쁜 예
try:
process_data()
except Exception:
pass # 완전히 무시
# ✅ 좋은 예
try:
process_data()
except Exception as e:
logging.error(f"처리 실패: {e}")
|
- 적절한 로깅
1
2
3
4
5
6
7
| import logging
try:
result = risky_operation()
except ValueError as e:
logging.exception("작업 실패") # 스택 트레이스 포함
raise
|
- 디버깅 시 전체 스택 트레이스 출력
1
2
3
4
5
6
7
| import traceback
try:
risky_operation()
except Exception as e:
traceback.print_exc() # 전체 에러 추적 정보
raise # 예외 재발생
|
⚠️ 주의사항
- 너무 넓은 범위의
except는 디버깅을 어렵게 만듭니다 - 예외를 잡았으면 반드시 처리하거나 로깅하세요
finally는 리소스 정리에만 사용하세요 Exception을 잡으면 모든 하위 예외가 잡힙니다
🧪 연습 문제
문제 1: 안전한 리스트 접근
목표: 리스트에서 인덱스로 값을 가져오되, 범위를 벗어나면 기본값을 반환하는 함수를 작성하세요.
요구사항:
- 함수명:
safe_get(lst, index, default=None) IndexError와 TypeError 처리 - 기본값 반환 기능
해답 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| def safe_get(lst, index, default=None):
"""안전한 리스트 접근"""
try:
return lst[index]
except IndexError:
return default
except TypeError:
return default
# 테스트
numbers = [1, 2, 3, 4, 5]
print(safe_get(numbers, 2)) # 3
print(safe_get(numbers, 10, 0)) # 0
print(safe_get(None, 0, -1)) # -1
|
문제 2: 파일 복사 (예외 처리)
목표: 파일을 복사하는 함수를 작성하되, 발생할 수 있는 모든 오류를 안전하게 처리하세요.
요구사항:
- 함수명:
safe_copy_file(source, destination) FileNotFoundError, PermissionError 처리 - 성공/실패 여부 반환
해답 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| def safe_copy_file(source, destination):
"""안전한 파일 복사"""
try:
with open(source, 'rb') as src:
with open(destination, 'wb') as dst:
dst.write(src.read())
print(f"✅ 복사 완료: {source} → {destination}")
return True
except FileNotFoundError:
print(f"❌ 원본 파일 없음: {source}")
return False
except PermissionError:
print(f"❌ 권한 없음")
return False
except Exception as e:
print(f"❌ 복사 실패: {e}")
return False
# 테스트
safe_copy_file('source.txt', 'backup.txt')
|
📝 오늘 배운 내용 정리
| 개념 | 설명 | 예시 |
| try-except | 예외 처리 기본 구조 | try: ... except ValueError: ... |
| 예외 타입 | 다양한 내장 예외 | ValueError, TypeError, KeyError |
| else 절 | 예외가 없을 때 실행 | try: ... except: ... else: ... |
| finally 절 | 항상 실행 (리소스 정리) | try: ... finally: f.close() |
| raise | 예외 발생시키기 | raise ValueError("오류") |
| 예외 객체 | 예외 정보 접근 | except ValueError as e: |
핵심 코드 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # 기본 예외 처리
try:
result = risky_operation()
except ValueError as e:
print(f"값 오류: {e}")
except TypeError:
print("타입 오류")
else:
print("성공!")
finally:
cleanup()
# 여러 예외 한 번에
try:
result = operation()
except (ValueError, TypeError) as e:
print(f"오류: {e}")
# 안전한 함수 패턴
def safe_operation(data, default=None):
try:
return process(data)
except Exception as e:
logging.error(f"실패: {e}")
return default
|
🔗 관련 자료
📚 이전 학습
Day 44: CSV 파일 처리 ⭐⭐⭐
어제는 CSV 모듈과 Pandas로 표 형식 데이터를 읽고 쓰고 분석하는 방법을 배웠습니다!
📚 다음 학습
Day 46: 예외 처리 고급 ⭐⭐⭐
내일은 사용자 정의 예외, 예외 체이닝, 컨텍스트 매니저 등 고급 예외 처리 기법을 배웁니다!
“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀
| Day 45/100 | Phase 5: 파일 처리와 예외 처리 | #100DaysOfPython |
이제와서 시작하는 Python 마스터하기 - Day 45 완료! 🎉