들어가며
이제 배운 모든 개념을 종합하여 실전 프로젝트를 만들어봅시다. 객체지향 설계, 에러 처리, 파일 I/O, 데이터 처리 등을 모두 활용합니다.
프로젝트 1: 학생 성적 관리 시스템
요구사항
- 학생 정보 저장 및 관리
- 성적 입력 및 통계 계산
- CSV 파일 저장/로드
- 성적 분포 분석
구현
import csv
import json
from statistics import mean, median, stdev
from datetime import datetime
class Student:
"""학생 정보를 관리하는 클래스"""
def __init__(self, student_id, name):
self.student_id = student_id
self.name = name
self.scores = []
def add_score(self, subject, score):
"""과목 점수 추가"""
if not (0 <= score <= 100):
raise ValueError("점수는 0~100 범위여야 합니다")
self.scores.append({"subject": subject, "score": score})
def get_average(self):
"""평균 점수"""
if not self.scores:
return 0
return mean(s["score"] for s in self.scores)
def get_stats(self):
"""통계 정보"""
if len(self.scores) < 2:
return None
scores = [s["score"] for s in self.scores]
return {
"average": mean(scores),
"median": median(scores),
"std_dev": stdev(scores),
"min": min(scores),
"max": max(scores)
}
class GradeBook:
"""성적 관리 시스템"""
def __init__(self):
self.students = {}
def add_student(self, student_id, name):
"""학생 추가"""
if student_id in self.students:
raise ValueError("이미 존재하는 학생입니다")
self.students[student_id] = Student(student_id, name)
def add_grade(self, student_id, subject, score):
"""학생 성적 추가"""
if student_id not in self.students:
raise ValueError("존재하지 않는 학생입니다")
self.students[student_id].add_score(subject, score)
def get_student_stats(self, student_id):
"""학생 통계 조회"""
if student_id not in self.students:
raise ValueError("존재하지 않는 학생입니다")
student = self.students[student_id]
return {
"id": student.student_id,
"name": student.name,
"average": student.get_average(),
"stats": student.get_stats()
}
def save_to_csv(self, filename):
"""CSV 파일로 저장"""
with open(filename, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(["학생ID", "이름", "과목", "점수"])
for student_id, student in self.students.items():
for score_info in student.scores:
writer.writerow([
student.student_id,
student.name,
score_info["subject"],
score_info["score"]
])
def load_from_csv(self, filename):
"""CSV 파일에서 로드"""
with open(filename, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
student_id = row["학생ID"]
if student_id not in self.students:
self.add_student(student_id, row["이름"])
self.add_grade(student_id, row["과목"], int(row["점수"]))
# 사용 예제
def main():
gradebook = GradeBook()
# 학생 추가
gradebook.add_student("001", "Alice")
gradebook.add_student("002", "Bob")
# 성적 입력
gradebook.add_grade("001", "수학", 85)
gradebook.add_grade("001", "영어", 90)
gradebook.add_grade("001", "과학", 88)
gradebook.add_grade("002", "수학", 92)
gradebook.add_grade("002", "영어", 87)
gradebook.add_grade("002", "과학", 95)
# 통계 출력
for student_id in ["001", "002"]:
stats = gradebook.get_student_stats(student_id)
print(f"학생: {stats['name']}")
print(f" 평균: {stats['average']:.2f}")
if stats['stats']:
print(f" 중앙값: {stats['stats']['median']:.2f}")
print(f" 표준편차: {stats['stats']['std_dev']:.2f}")
print()
# CSV 저장
gradebook.save_to_csv("grades.csv")
print("CSV 파일로 저장되었습니다")
if __name__ == "__main__":
main()
출력:
학생: Alice
평균: 87.67
중앙값: 88.00
표준편차: 2.05
학생: Bob
평균: 91.33
중앙값: 92.00
표준편차: 4.04
CSV 파일로 저장되었습니다
프로젝트 2: 데이터 분석 파이프라인
요구사항
- 로그 파일 분석
- 데이터 필터링 및 변환
- 결과 통계
- 효율적인 메모리 사용
구현
import logging
from functools import lru_cache
from collections import Counter
class LogAnalyzer:
"""로그 파일 분석 클래스"""
def __init__(self, filename):
self.filename = filename
self.setup_logging()
def setup_logging(self):
"""로깅 설정"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
self.logger = logging.getLogger(__name__)
def parse_log_line(self, line):
"""로그 라인 파싱"""
try:
parts = line.split(' - ')
return {
"timestamp": parts[0],
"level": parts[1],
"message": ' - '.join(parts[2:])
}
except:
return None
def read_logs(self):
"""로그 파일 읽기 (제너레이터)"""
try:
with open(self.filename, 'r') as f:
for line in f:
parsed = self.parse_log_line(line.strip())
if parsed:
yield parsed
self.logger.info("로그 파일 읽기 완료")
except FileNotFoundError:
self.logger.error(f"파일을 찾을 수 없습니다: {self.filename}")
def filter_logs(self, level=None):
"""특정 로그 레벨만 필터링"""
for log in self.read_logs():
if level is None or log['level'] == level:
yield log
def analyze(self):
"""전체 분석"""
try:
logs = list(self.read_logs())
stats = {
"total": len(logs),
"by_level": Counter(log['level'] for log in logs),
"sample_messages": [log['message'] for log in logs[:3]]
}
self.logger.info(f"분석 완료: {stats['total']}개 로그")
return stats
except Exception as e:
self.logger.error(f"분석 중 오류: {e}")
raise
# 사용 예제
def create_sample_log():
"""샘플 로그 파일 생성"""
with open("app.log", "w") as f:
logs = [
"2024-01-15 10:00:00 - INFO - 애플리케이션 시작",
"2024-01-15 10:00:01 - INFO - 데이터베이스 연결",
"2024-01-15 10:00:02 - WARNING - 캐시 미스",
"2024-01-15 10:00:03 - ERROR - 데이터베이스 오류",
"2024-01-15 10:00:04 - INFO - 재시도 시작",
"2024-01-15 10:00:05 - INFO - 성공",
]
for log in logs:
f.write(log + "\n")
def main():
create_sample_log()
analyzer = LogAnalyzer("app.log")
# 전체 분석
stats = analyzer.analyze()
print(f"총 로그: {stats['total']}")
print(f"레벨별 로그:")
for level, count in stats['by_level'].items():
print(f" {level}: {count}")
if __name__ == "__main__":
main()
출력:
총 로그: 6
레벨별 로그:
INFO: 4
WARNING: 1
ERROR: 1
프로젝트 3: 설정 관리자
요구사항
- JSON 설정 파일 로드/저장
- 기본값 지정
- 설정 검증
- 싱글톤 패턴
구현
import json
import os
from typing import Any
class ConfigManager:
"""설정 관리 클래스 (싱글톤)"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self.config = {}
self.defaults = {}
self._initialized = True
def set_defaults(self, defaults: dict):
"""기본값 설정"""
self.defaults = defaults
self.config = defaults.copy()
def load(self, filename: str):
"""파일에서 로드"""
try:
if os.path.exists(filename):
with open(filename, 'r', encoding='utf-8') as f:
file_config = json.load(f)
self.config.update(file_config)
print(f"설정 로드됨: {filename}")
else:
print(f"파일 없음: {filename}")
except Exception as e:
print(f"로드 실패: {e}")
def save(self, filename: str):
"""파일에 저장"""
try:
with open(filename, 'w', encoding='utf-8') as f:
json.dump(self.config, f, indent=2, ensure_ascii=False)
print(f"설정 저장됨: {filename}")
except Exception as e:
print(f"저장 실패: {e}")
def get(self, key: str, default=None) -> Any:
"""설정값 조회"""
return self.config.get(key, default)
def set(self, key: str, value: Any):
"""설정값 설정"""
self.config[key] = value
def validate(self, rules: dict) -> bool:
"""설정 검증"""
for key, rule in rules.items():
value = self.get(key)
# 필수 값 확인
if rule.get('required', False) and value is None:
print(f"오류: {key}는 필수입니다")
return False
# 타입 확인
if 'type' in rule and value is not None:
if not isinstance(value, rule['type']):
print(f"오류: {key}는 {rule['type'].__name__} 타입이어야 합니다")
return False
return True
# 사용 예제
def main():
# 설정 관리자 초기화
config = ConfigManager()
# 기본값 설정
defaults = {
"app_name": "MyApp",
"debug": True,
"port": 8000,
"database_url": "sqlite:///app.db"
}
config.set_defaults(defaults)
# 설정값 조회
print(f"앱 이름: {config.get('app_name')}")
print(f"디버그: {config.get('debug')}")
print(f"포트: {config.get('port')}")
# 설정값 수정
config.set('port', 3000)
config.set('debug', False)
# 파일에 저장
config.save("config.json")
# 검증
rules = {
"app_name": {"required": True, "type": str},
"port": {"required": True, "type": int},
"debug": {"required": True, "type": bool}
}
if config.validate(rules):
print("설정 검증 통과")
# 싱글톤 확인
config2 = ConfigManager()
print(f"\n싱글톤 확인: {config is config2}")
if __name__ == "__main__":
main()
출력:
앱 이름: MyApp
디버그: True
포트: 8000
설정 저장됨: config.json
설정 검증 통과
싱글톤 확인: True
핵심 학습 정리
설계 원칙
SOLID 원칙
- Single Responsibility: 클래스는 한 가지 책임만
- Open/Closed: 확장에는 열려있고 수정에는 닫혀있음
- Liskov Substitution: 상속 구조는 치환 가능해야 함
- Interface Segregation: 클라이언트별 인터페이스 분리
- Dependency Inversion: 추상화에 의존
디자인 패턴
- 싱글톤: 인스턴스가 하나만 존재
- 팩토리: 객체 생성 방식 추상화
- 데코레이터: 객체의 기능 동적 확장
- 옵저버: 상태 변화 통지
성능 최적화
- 제너레이터로 메모리 절약
- 캐싱으로 반복 계산 방지
- 알고리즘 최적화
코드 품질
# 좋은 코드의 특징
class WellDesignedClass:
"""
이 클래스는 다음을 만족합니다:
- 명확한 목적과 책임
- 완전한 문서화
- 예외 처리
- 테스트 가능한 구조
"""
def __init__(self, param):
"""생성자 문서"""
self.param = param
def method(self):
"""메서드 문서"""
try:
# 구현
pass
except Exception as e:
# 적절한 예외 처리
pass
다음 단계
테스트 작성
- Unit 테스트
- Integration 테스트
- Test-Driven Development
성능 분석
- 프로파일링
- 병목 지점 파악
- 최적화
배포 및 운영
- 환경 설정 분리
- 로깅 및 모니터링
- 문서화
마무리
지금까지 배운 모든 개념들을 활용하여 실전 프로젝트를 만들어봤습니다. 좋은 코드는 다음을 만족합니다:
- 가독성: 다른 사람이 쉽게 이해할 수 있음
- 유지보수성: 변경과 확장이 용이함
- 견고성: 예외 상황을 적절히 처리
- 효율성: 시간과 공간 측면에서 최적화
- 문서화: 충분한 문서와 주석
계속해서 실제 프로젝트를 만들고, 오픈소스를 기여하며, 다른 개발자의 코드를 읽으며 성장하세요. Happy coding!