KR_BestPractices - somaz94/python-study GitHub Wiki

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 ์›์น™, ์ ์ ˆํ•œ ์ถ”์ƒํ™”
  • ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ณ ๋ ค: ๊ธฐ๋Šฅ๋ณ„ ๋ถ„๋ฆฌ, ํ™•์žฅ์„ฑ ํ™•๋ณด


โš ๏ธ **GitHub.com Fallback** โš ๏ธ