| name | python-patterns |
| description | Protocol, Dataclass, Context manager, Decorator, Async/await, Type hint 및 패키지 구조를 포함한 Python 전용 디자인 패턴 및 모범 사례. Pythonic한 패턴을 적용하기 위해 Python 코드를 작업할 때 사용하세요.
|
| metadata | {"origin":"ECC","globs":["**/*.py","**/*.pyi"]} |
Python 패턴 (Python Patterns)
이 스킬은 일반적인 디자인 원칙을 Python 특유의 관용구(idioms)로 확장한 포괄적인 Python 패턴을 제공합니다.
프로토콜 (Duck Typing)
구조적 서브타이핑(타입 힌트가 포함된 덕 타이핑)을 위해 Protocol을 사용하세요:
from typing import Protocol
class Repository(Protocol):
def find_by_id(self, id: str) -> dict | None: ...
def save(self, entity: dict) -> dict: ...
class UserRepository:
def find_by_id(self, id: str) -> dict | None:
pass
def save(self, entity: dict) -> dict:
pass
def process_entity(repo: Repository, id: str) -> None:
entity = repo.find_by_id(id)
이점:
- 상속 없이 타입 안정성 확보
- 유연하고 결합도가 낮은 코드
- 쉬운 테스트 및 모킹(mocking)
DTO로서의 데이터 클래스 (Dataclasses)
데이터 전송 객체(DTO) 및 값 객체(Value Object)를 위해 dataclass를 사용하세요:
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class CreateUserRequest:
name: str
email: str
age: Optional[int] = None
tags: list[str] = field(default_factory=list)
@dataclass(frozen=True)
class User:
"""불변(Immutable) 사용자 엔티티"""
id: str
name: str
email: str
특징:
__init__, __repr__, __eq__ 자동 생성
frozen=True를 통한 불변성 보장
- 복잡한 기본값을 위한
field() 제공
- 검증을 위한 타입 힌트 활용
컨텍스트 매니저 (Context Managers)
리소스 관리를 위해 컨텍스트 매니저(with 문)를 사용하세요:
from contextlib import contextmanager
from typing import Generator
@contextmanager
def database_transaction(db) -> Generator[None, None, None]:
"""데이터베이스 트랜잭션을 위한 컨텍스트 매니저"""
try:
yield
db.commit()
except Exception:
db.rollback()
raise
with database_transaction(db):
db.execute("INSERT INTO users ...")
클래스 기반 컨텍스트 매니저:
class FileProcessor:
def __init__(self, filename: str):
self.filename = filename
self.file = None
def __enter__(self):
self.file = open(self.filename, 'r')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
return False
제너레이터 (Generators)
지연 평가(lazy evaluation) 및 메모리 효율적인 반복을 위해 제너레이터를 사용하세요:
def read_large_file(filename: str):
"""대용량 파일을 한 줄씩 읽는 제너레이터"""
with open(filename, 'r') as f:
for line in f:
yield line.strip()
for line in read_large_file('huge.txt'):
process(line)
제너레이터 표현식:
squares = (x**2 for x in range(1000000))
numbers = (x for x in range(100))
evens = (x for x in numbers if x % 2 == 0)
squares = (x**2 for x in evens)
데코레이터 (Decorators)
함수 데코레이터
from functools import wraps
import time
def timing(func):
"""실행 시간을 측정하는 데코레이터"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 실행 시간: {end - start:.2f}초")
return result
return wrapper
@timing
def slow_function():
time.sleep(1)
클래스 데코레이터
def singleton(cls):
"""클래스를 싱글톤으로 만드는 데코레이터"""
instances = {}
@wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Config:
pass
Async/Await
비동기 함수
import asyncio
from typing import List
async def fetch_user(user_id: str) -> dict:
"""I/O 바운드 작업을 위한 비동기 함수"""
await asyncio.sleep(0.1)
return {"id": user_id, "name": "Alice"}
async def fetch_all_users(user_ids: List[str]) -> List[dict]:
"""asyncio.gather를 사용한 병렬 실행"""
tasks = [fetch_user(uid) for uid in user_ids]
return await asyncio.gather(*tasks)
asyncio.run(fetch_all_users(["1", "2", "3"]))
비동기 컨텍스트 매니저
class AsyncDatabase:
async def __aenter__(self):
await self.connect()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.disconnect()
async with AsyncDatabase() as db:
await db.query("SELECT * FROM users")
타입 힌트 (Type Hints)
고급 타입 힌트
from typing import TypeVar, Generic, Callable, ParamSpec, Concatenate
T = TypeVar('T')
P = ParamSpec('P')
class Repository(Generic[T]):
"""제네릭 리포지토리 패턴"""
def __init__(self, entity_type: type[T]):
self.entity_type = entity_type
def find_by_id(self, id: str) -> T | None:
pass
def log_call(func: Callable[P, T]) -> Callable[P, T]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
유니온 타입 (Python 3.10+)
def process(value: str | int | None) -> str:
match value:
case str():
return value.upper()
case int():
return str(value)
case None:
return "empty"
의존성 주입 (Dependency Injection)
생성자 주입
class UserService:
def __init__(
self,
repository: Repository,
logger: Logger,
cache: Cache | None = None
):
self.repository = repository
self.logger = logger
self.cache = cache
def get_user(self, user_id: str) -> User | None:
if self.cache:
cached = self.cache.get(user_id)
if cached:
return cached
user = self.repository.find_by_id(user_id)
if user and self.cache:
self.cache.set(user_id, user)
return user
패키지 조직 (Package Organization)
프로젝트 구조
project/
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── domain/ # 비즈니스 로직
│ │ ├── __init__.py
│ │ └── models.py
│ ├── services/ # 애플리케이션 서비스
│ │ ├── __init__.py
│ │ └── user_service.py
│ └── infrastructure/ # 외부 의존성
│ ├── __init__.py
│ └── database.py
├── tests/
│ ├── unit/
│ └── integration/
├── pyproject.toml
└── README.md
모듈 내보내기 (Exports)
from .models import User, Product
from .services import UserService
__all__ = ['User', 'Product', 'UserService']
에러 처리
커스텀 예외
class DomainError(Exception):
"""도메인 에러를 위한 기본 예외 클래스"""
pass
class UserNotFoundError(DomainError):
"""사용자를 찾을 수 없을 때 발생"""
def __init__(self, user_id: str):
self.user_id = user_id
super().__init__(f"User {user_id} not found")
class ValidationError(DomainError):
"""검증 실패 시 발생"""
def __init__(self, field: str, message: str):
self.field = field
self.message = message
super().__init__(f"{field}: {message}")
예외 그룹 (Python 3.11+)
try:
pass
except* ValueError as eg:
for exc in eg.exceptions:
print(f"ValueError: {exc}")
except* TypeError as eg:
for exc in eg.exceptions:
print(f"TypeError: {exc}")
프로퍼티 데코레이터 (Property Decorators)
class User:
def __init__(self, name: str):
self._name = name
self._email = None
@property
def name(self) -> str:
"""읽기 전용 프로퍼티"""
return self._name
@property
def email(self) -> str | None:
return self._email
@email.setter
def email(self, value: str) -> None:
if '@' not in value:
raise ValueError("Invalid email")
self._email = value
함수형 프로그래밍
고차 함수 (Higher-Order Functions)
from functools import reduce
from typing import Callable, TypeVar
T = TypeVar('T')
U = TypeVar('U')
def pipe(*functions: Callable) -> Callable:
"""함수들을 왼쪽에서 오른쪽으로 합성"""
def inner(arg):
return reduce(lambda x, f: f(x), functions, arg)
return inner
process = pipe(
str.strip,
str.lower,
lambda s: s.replace(' ', '_')
)
result = process(" Hello World ")
이 스킬을 사용하는 시점
- Python API 및 패키지를 설계할 때
- 비동기/병렬 시스템을 구현할 때
- Python 프로젝트 구조를 잡을 때
- Pythonic한 코드를 작성할 때
- Python 코드베이스를 리팩터링할 때
- 타입 안전한 Python 개발을 할 때