Skip to content

KR_BestPractices

somaz edited this page Apr 22, 2025 · 4 revisions

Python Best Pratices 사례


1️⃣ 코드 스타일과 구조

명확하고 일관된 코드 작성 방법을 설명한다.

# 1. 명확한 변수명과 함수명 사용
# 나쁜 예
def f(x):
    return x * 2

# 좋은 예
def double_number(number: int) -> int:
    return number * 2

# 2. 클래스 구조화
class User:
    """사용자 정보를 관리하는 클래스"""
    
    def __init__(self, name: str, email: str):
        self.name = name
        self.email = email
    
    @property
    def display_name(self) -> str:
        return f"{self.name} <{self.email}>"

특징:

  • 명확한 이름 사용
  • 타입 힌트 활용
  • 문서화 주석 추가


2️⃣ 에러 처리

효과적인 예외 처리 및 에러 관리 방법을 다룬다.

from typing import Any, Dict, Optional
import logging

class CustomError(Exception):
    """사용자 정의 예외"""
    def __init__(self, message: str, error_code: int):
        self.message = message
        self.error_code = error_code
        super().__init__(self.message)

def safe_operation(func):
    """에러 처리 데코레이터"""
    def wrapper(*args, **kwargs) -> Dict[str, Any]:
        try:
            result = func(*args, **kwargs)
            return {'success': True, 'data': result}
        except CustomError as e:
            logging.error(f"Custom error: {e.message}")
            return {
                'success': False,
                'error': e.message,
                'error_code': e.error_code
            }
        except Exception as e:
            logging.exception("Unexpected error occurred")
            return {
                'success': False,
                'error': str(e),
                'error_code': 500
            }
    return wrapper

특징:

  • 커스텀 예외 정의
  • 로깅 활용
  • 데코레이터 패턴 사용
  • 구조화된 에러 응답
  • 상세한 에러 정보 제공


3️⃣ 설정 관리

애플리케이션 설정을 효율적으로 관리하는 방법이다.

from dataclasses import dataclass
from typing import Optional
import yaml
import os

@dataclass
class DatabaseConfig:
    host: str
    port: int
    username: str
    password: str
    database: str

@dataclass
class AppConfig:
    debug: bool
    secret_key: str
    db: DatabaseConfig

class ConfigManager:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self):
        self.config = self.load_config()
    
    def load_config(self) -> AppConfig:
        env = os.getenv('APP_ENV', 'development')
        config_path = f'config/{env}.yml'
        
        with open(config_path) as f:
            config_data = yaml.safe_load(f)
        
        return AppConfig(
            debug=config_data['debug'],
            secret_key=config_data['secret_key'],
            db=DatabaseConfig(**config_data['database'])
        )

특징:

  • 데이터클래스 활용
  • 싱글톤 패턴 구현
  • 환경별 설정 분리
  • 타입 안전성 보장
  • 계층적 구조화


4️⃣ 테스트 작성

품질 높은 코드를 위한 테스트 작성 방법을 설명한다.

import pytest
from typing import List, Dict

class Calculator:
    def add(self, x: int, y: int) -> int:
        return x + y
    
    def divide(self, x: int, y: int) -> float:
        if y == 0:
            raise ValueError("Cannot divide by zero")
        return x / y

class TestCalculator:
    @pytest.fixture
    def calculator(self):
        return Calculator()
    
    def test_add(self, calculator):
        assert calculator.add(2, 3) == 5
    
    def test_divide(self, calculator):
        assert calculator.divide(6, 2) == 3.0
    
    def test_divide_by_zero(self, calculator):
        with pytest.raises(ValueError):
            calculator.divide(1, 0)

특징:

  • pytest 활용
  • 픽스처 사용
  • 예외 테스트 포함
  • 명확한 테스트 구조
  • 단위 테스트 분리


5️⃣ 문서화

효과적인 코드 문서화 방법과 도구를 설명한다.

from typing import List, Optional

class DocumentationExample:
    """
    문서화 예시 클래스
    
    이 클래스는 파이썬 문서화의 모범 사례를 보여준다.
    
    Attributes:
        name (str): 객체의 이름
        value (int): 객체의 값
    """
    
    def __init__(self, name: str, value: int):
        self.name = name
        self.value = value
    
    def process_data(self, data: List[int]) -> Optional[float]:
        """
        데이터를 처리하고 결과를 반환한다.
        
        Args:
            data (List[int]): 처리할 정수 리스트
        
        Returns:
            Optional[float]: 처리된 결과값, 실패시 None
        
        Raises:
            ValueError: 빈 리스트가 입력된 경우
        """
        if not data:
            raise ValueError("Empty data list")
        
        try:
            return sum(data) / len(data)
        except Exception:
            return None

특징:

  • 도큐멘트 스트링 활용
  • 타입 힌트 포함
  • 예외 명세 기록
  • 일관된 문서화 스타일
  • 속성 및 메서드 설명


6️⃣ 실용적인 예제

실제 애플리케이션에서 사용할 수 있는 모범 사례 예제를 살펴본다.

API 설계:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional

class UserBase(BaseModel):
    name: str
    email: str

class UserCreate(UserBase):
    password: str

class User(UserBase):
    id: int
    is_active: bool

class UserAPI:
    def __init__(self):
        self.app = FastAPI()
        self.setup_routes()
    
    def setup_routes(self):
        @self.app.post("/users/", response_model=User)
        async def create_user(user: UserCreate):
            return await self.create_user_handler(user)
    
    async def create_user_handler(self, user: UserCreate) -> User:
        # 사용자 생성 로직
        pass

특징:

  • FastAPI 활용
  • Pydantic 모델 사용
  • 컨텍스트 매니저 활용

데이터베이스 접근:

from contextlib import contextmanager
from typing import Generator
from sqlalchemy.orm import Session
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

class DatabaseManager:
    def __init__(self, connection_string: str):
        self.engine = create_engine(connection_string)
        self.SessionLocal = sessionmaker(bind=self.engine)
    
    @contextmanager
    def get_session(self) -> Generator[Session, None, None]:
        session = self.SessionLocal()
        try:
            yield session
            session.commit()
        except Exception:
            session.rollback()
            raise
        finally:
            session.close()
    
    def execute_transaction(self, operations):
        with self.get_session() as session:
            return operations(session)

특징:

  • 데이터베이스 접근 패턴 보여줌

비동기 작업 처리:

import asyncio
from typing import List, Dict, Any
import aiohttp

class AsyncDataFetcher:
    def __init__(self, base_url: str):
        self.base_url = base_url
        self.session = None
    
    async def __aenter__(self):
        self.session = aiohttp.ClientSession()
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.session:
            await self.session.close()
    
    async def fetch_data(self, endpoint: str) -> Dict[str, Any]:
        if not self.session:
            raise RuntimeError("세션이 초기화되지 않았습니다. 컨텍스트 매니저로 사용하세요.")
        
        url = f"{self.base_url}/{endpoint}"
        async with self.session.get(url) as response:
            if response.status != 200:
                raise ValueError(f"데이터 가져오기 실패: {response.status}")
            return await response.json()
    
    async def fetch_multiple(self, endpoints: List[str]) -> List[Dict[str, Any]]:
        tasks = [self.fetch_data(endpoint) for endpoint in endpoints]
        return await asyncio.gather(*tasks, return_exceptions=True)

# 사용 예:
async def main():
    async with AsyncDataFetcher("https://api.example.com") as fetcher:
        data = await fetcher.fetch_multiple(["users", "products", "orders"])
        print(data)

# 실행
# asyncio.run(main())

특징:

  • 비동기 코드 구성
  • 컨텍스트 매니저 활용
  • 타입 안전성 보장
  • 에러 처리 고려

로깅 및 모니터링:

import logging
import time
from functools import wraps
from typing import Any, Callable, Dict

# 로거 설정
def setup_logger(name: str, level=logging.INFO) -> logging.Logger:
    logger = logging.getLogger(name)
    logger.setLevel(level)
    
    if not logger.handlers:
        handler = logging.StreamHandler()
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        )
        handler.setFormatter(formatter)
        logger.addHandler(handler)
    
    return logger

# 성능 측정 데코레이터
def measure_performance(logger: logging.Logger):
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            start_time = time.time()
            result = func(*args, **kwargs)
            execution_time = time.time() - start_time
            
            logger.info(f"함수 {func.__name__} 실행 시간: {execution_time:.4f}초")
            return result
        return wrapper
    return decorator

# 사용 예시
logger = setup_logger("app_logger")

@measure_performance(logger)
def process_data(data: Dict[str, Any]) -> Dict[str, Any]:
    # 데이터 처리 로직
    time.sleep(0.5)  # 처리 시간 시뮬레이션
    return {"processed": True, "original": data}

# 실행
# result = process_data({"id": 1, "value": "test"})

특징:

  • 로깅 및 성능 모니터링
  • 컨텍스트 매니저 활용
  • 타입 안전성 보장
  • 에러 처리 고려

특징:

  • API 디자인 패턴
  • 데이터베이스 트랜잭션 관리
  • 비동기 코드 구성
  • 로깅 및 성능 모니터링
  • 컨텍스트 매니저 활용
  • 타입 안전성 보장
  • 에러 처리 고려

7️⃣ 코드 최적화

파이썬 코드의 성능을 개선하는 방법을 설명한다.

데이터 구조 최적화:

from collections import defaultdict, deque
from typing import List, Dict, Set, Deque

# 리스트보다 효율적인 작업을 위한 데크 활용
def sliding_window(data: List[int], window_size: int) -> List[List[int]]:
    """
    데크를 사용한 효율적인 슬라이딩 윈도우 구현
    """
    result = []
    window: Deque[int] = deque(maxlen=window_size)
    
    for item in data:
        window.append(item)
        if len(window) == window_size:
            result.append(list(window))
    
    return result

# 루프 대신 딕셔너리 활용
def count_occurrences(items: List[str]) -> Dict[str, int]:
    """
    각 항목의 발생 횟수를 계산
    """
    counter = defaultdict(int)
    for item in items:
        counter[item] += 1
    return dict(counter)

# 집합을 사용한 효율적인 중복 제거
def find_unique(items: List[str]) -> List[str]:
    """
    집합을 사용하여 중복 제거 및 순서 유지
    """
    seen: Set[str] = set()
    return [item for item in items if not (item in seen or seen.add(item))]

성능 최적화 기법:

import functools
import time
from typing import Dict, Any, Callable, TypeVar

T = TypeVar('T')

# 메모이제이션을 통한 비용이 많이 드는 계산 최적화
@functools.lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    """
    피보나치 수열 계산 (캐싱 사용)
    """
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 제너레이터를 사용한 메모리 효율적인 처리
def process_large_file(file_path: str, chunk_size: int = 1024):
    """
    대용량 파일을 청크 단위로 처리하는 제너레이터
    """
    with open(file_path, 'r') as f:
        while True:
            data = f.read(chunk_size)
            if not data:
                break
            yield data

# 리스트 컴프리헨션 사용 (루프보다 효율적)
def transform_data(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """
    리스트 컴프리헨션을 사용한 데이터 변환
    """
    return [{
        'id': item['id'],
        'name': item['name'].upper(),
        'value': item['value'] * 2
    } for item in data if item['active']]

특징:

  • 적절한 자료구조 선택
  • 메모이제이션 활용
  • 제너레이터와 이터레이터 활용
  • 리스트 컴프리헨션 최적화
  • 불필요한 계산 방지
  • 메모리 사용량 고려


8️⃣ 보안 모범 사례

파이썬 애플리케이션의 보안을 강화하는 방법을 설명한다.

안전한 비밀번호 처리:

import hashlib
import os
import hmac
import binascii
from typing import Tuple

def hash_password(password: str) -> Tuple[str, str]:
    """
    비밀번호를 안전하게 해싱하고 솔트와 해시를 반환한다
    
    Args:
        password: 해싱할 원본 비밀번호
        
    Returns:
        (salt, hash) 튜플
    """
    # 랜덤 솔트 생성
    salt = os.urandom(32)
    
    # 비밀번호 해싱
    pw_hash = hashlib.pbkdf2_hmac(
        'sha256',
        password.encode('utf-8'),
        salt,
        100000  # 반복 횟수
    )
    
    # 16진수 문자열로 변환
    salt_hex = binascii.hexlify(salt).decode('utf-8')
    hash_hex = binascii.hexlify(pw_hash).decode('utf-8')
    
    return salt_hex, hash_hex

def verify_password(password: str, salt_hex: str, stored_hash: str) -> bool:
    """
    입력된 비밀번호가 저장된 해시와 일치하는지 확인한다
    
    Args:
        password: 확인할 비밀번호
        salt_hex: 저장된 솔트(16진수 문자열)
        stored_hash: 저장된 해시(16진수 문자열)
        
    Returns:
        비밀번호 일치 여부
    """
    # 솔트를 바이트로 변환
    salt = binascii.unhexlify(salt_hex)
    
    # 입력된 비밀번호 해싱
    pw_hash = hashlib.pbkdf2_hmac(
        'sha256',
        password.encode('utf-8'),
        salt,
        100000  # 반복 횟수 (저장 시와 동일)
    )
    
    # 해시 비교 (타이밍 공격 방지를 위해 hmac.compare_digest 사용)
    new_hash = binascii.hexlify(pw_hash).decode('utf-8')
    return hmac.compare_digest(new_hash, stored_hash)

SQL 인젝션 방지:

import sqlite3
from typing import List, Dict, Any, Tuple

class SafeDatabase:
    """안전한 데이터베이스 작업을 위한 클래스"""
    
    def __init__(self, db_path: str):
        self.db_path = db_path
        self.connection = None
    
    def connect(self):
        self.connection = sqlite3.connect(self.db_path)
        self.connection.row_factory = sqlite3.Row
    
    def close(self):
        if self.connection:
            self.connection.close()
    
    def __enter__(self):
        self.connect()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
    
    def safe_query(self, query: str, params: Tuple = ()) -> List[Dict[str, Any]]:
        """
        매개변수화된 쿼리를 안전하게 실행한다
        
        Args:
            query: SQL 쿼리(매개변수는 ? 사용)
            params: 쿼리 매개변수
            
        Returns:
            쿼리 결과 리스트
        """
        cursor = self.connection.cursor()
        cursor.execute(query, params)
        
        results = [dict(row) for row in cursor.fetchall()]
        cursor.close()
        
        return results
    
    # 잘못된 방법 (참고용으로만 표시)
    def unsafe_query(self, user_input: str) -> List[Dict[str, Any]]:
        """
        절대 사용하면 안 되는 방법
        SQL 인젝션에 취약함
        """
        query = f"SELECT * FROM users WHERE username = '{user_input}'"
        cursor = self.connection.cursor()
        cursor.execute(query)
        
        results = [dict(row) for row in cursor.fetchall()]
        cursor.close()
        
        return results

특징:

  • 안전한 비밀번호 해싱
  • 솔트 사용
  • 매개변수화된 SQL 쿼리
  • 보안 취약점 방지
  • 타이밍 공격 방어
  • 적절한 암호화 기법 사용

주요 팁

모범 사례:

  • 일관된 코드 스타일: PEP 8 준수, 자동 포맷터 사용(Black, YAPF)
  • 명확한 명명 규칙: 의미 있는 변수명, 함수명 사용
  • 타입 힌트 활용: 코드의 가독성과 안전성 향상
  • 적절한 에러 처리: 예외 계층 구조화, 상세한 에러 메시지
  • 환경 변수 분리: 개발, 테스트, 프로덕션 설정 구분
  • 철저한 테스트: 단위 테스트, 통합 테스트 작성
  • 문서화 습관화: 함수, 클래스, 모듈 수준의 문서화
  • 보안 고려: 안전한 암호화, 입력 검증
  • 성능 최적화: 적절한 자료구조, 알고리즘 선택
  • 코드 리뷰 활성화: 피어 리뷰를 통한 품질 개선
  • 디펜던시 관리: 의존성 최소화, 버전 고정
  • 로깅 전략 수립: 구조화된 로깅, 적절한 로그 레벨
  • 리팩토링 정기화: 기술 부채 누적 방지
  • 코드 재사용성: DRY 원칙, 적절한 추상화
  • 마이크로서비스 고려: 기능별 분리, 확장성 확보


Clone this wiki locally