// education

실전 프로젝트 총정리

들어가며

이제 배운 모든 개념을 종합하여 실전 프로젝트를 만들어봅시다. 객체지향 설계, 에러 처리, 파일 I/O, 데이터 처리 등을 모두 활용합니다.

프로젝트 1: 학생 성적 관리 시스템

요구사항

구현

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: 설정 관리자

요구사항

구현

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

핵심 학습 정리

설계 원칙

  1. SOLID 원칙

    • Single Responsibility: 클래스는 한 가지 책임만
    • Open/Closed: 확장에는 열려있고 수정에는 닫혀있음
    • Liskov Substitution: 상속 구조는 치환 가능해야 함
    • Interface Segregation: 클라이언트별 인터페이스 분리
    • Dependency Inversion: 추상화에 의존
  2. 디자인 패턴

    • 싱글톤: 인스턴스가 하나만 존재
    • 팩토리: 객체 생성 방식 추상화
    • 데코레이터: 객체의 기능 동적 확장
    • 옵저버: 상태 변화 통지
  3. 성능 최적화

    • 제너레이터로 메모리 절약
    • 캐싱으로 반복 계산 방지
    • 알고리즘 최적화

코드 품질

# 좋은 코드의 특징
class WellDesignedClass:
    """
    이 클래스는 다음을 만족합니다:
    - 명확한 목적과 책임
    - 완전한 문서화
    - 예외 처리
    - 테스트 가능한 구조
    """
    
    def __init__(self, param):
        """생성자 문서"""
        self.param = param
    
    def method(self):
        """메서드 문서"""
        try:
            # 구현
            pass
        except Exception as e:
            # 적절한 예외 처리
            pass

다음 단계

  1. 테스트 작성

    • Unit 테스트
    • Integration 테스트
    • Test-Driven Development
  2. 성능 분석

    • 프로파일링
    • 병목 지점 파악
    • 최적화
  3. 배포 및 운영

    • 환경 설정 분리
    • 로깅 및 모니터링
    • 문서화

마무리

지금까지 배운 모든 개념들을 활용하여 실전 프로젝트를 만들어봤습니다. 좋은 코드는 다음을 만족합니다:

계속해서 실제 프로젝트를 만들고, 오픈소스를 기여하며, 다른 개발자의 코드를 읽으며 성장하세요. Happy coding!

← 이전제너레이터와 효율적 코딩 다음 →이터레이터(Iterator), yield 제너레이터(Generator)와 데코레이터(Decorator)