From ecc
견고하고 효율적이며 유지보수가 쉬운 Python 애플리케이션을 구축하기 위한 Pythonic 관용구, PEP 8 표준, 타입 힌트 및 모범 사례입니다.
npx claudepluginhub sam42-lab/everything-claude-code-krThis skill uses the workspace's default tool permissions.
견고하고 효율적이며 유지보수가 쉬운 애플리케이션을 구축하기 위한 관용적인 Python 패턴 및 모범 사례입니다.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
견고하고 효율적이며 유지보수가 쉬운 애플리케이션을 구축하기 위한 관용적인 Python 패턴 및 모범 사례입니다.
Python은 가독성을 우선시합니다. 코드는 명확하고 이해하기 쉬워야 합니다.
# 좋음: 명확하고 읽기 쉬움
def get_active_users(users: list[User]) -> list[User]:
"""제공된 목록에서 활성 사용자만 반환합니다."""
return [user for user in users if user.is_active]
# 나쁨: 똑똑해 보이지만 혼란스러움
def get_active_users(u):
return [x for x in u if x.a]
마법 같은 처리를 피하고, 코드가 무엇을 하는지 명확하게 하십시오.
# 좋음: 명시적인 설정
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 나쁨: 숨겨진 부작용
import some_module
some_module.setup() # 이게 무엇을 하나요?
Python은 조건을 확인하는 것보다 예외 처리를 선호합니다.
# 좋음: EAFP 스타일
def get_value(dictionary: dict, key: str) -> Any:
try:
return dictionary[key]
except KeyError:
return default_value
# 나쁨: LBYL (Look Before You Leap) 스타일
def get_value(dictionary: dict, key: str) -> Any:
if key in dictionary:
return dictionary[key]
else:
return default_value
from typing import Optional, List, Dict, Any
def process_user(
user_id: str,
data: Dict[str, Any],
active: bool = True
) -> Optional[User]:
"""사용자를 처리하고 업데이트된 User 또는 None을 반환합니다."""
if not active:
return None
return User(user_id, data)
# Python 3.9+ - 내장 타입 사용
def process_items(items: list[str]) -> dict[str, int]:
return {item: len(item) for item in items}
# Python 3.8 및 이전 - typing 모듈 사용
from typing import List, Dict
def process_items(items: List[str]) -> Dict[str, int]:
return {item: len(item) for item in items}
from typing import TypeVar, Union
# 복잡한 타입을 위한 타입 별칭
JSON = Union[dict[str, Any], list[Any], str, int, float, bool, None]
def parse_json(data: str) -> JSON:
return json.loads(data)
# 제네릭 타입
T = TypeVar('T')
def first(items: list[T]) -> T | None:
"""목록의 첫 번째 항목을 반환하거나 목록이 비어 있으면 None을 반환합니다."""
return items[0] if items else None
from typing import Protocol
class Renderable(Protocol):
def render(self) -> str:
"""객체를 문자열로 렌더링합니다."""
def render_all(items: list[Renderable]) -> str:
"""Renderable 프로토콜을 구현하는 모든 항목을 렌더링합니다."""
return "\n".join(item.render() for item in items)
# 좋음: 특정 예외를 캡치
def load_config(path: str) -> Config:
try:
with open(path) as f:
return Config.from_json(f.read())
except FileNotFoundError as e:
raise ConfigError(f"설정 파일을 찾을 수 없음: {path}") from e
except json.JSONDecodeError as e:
raise ConfigError(f"설정 파일의 JSON이 유효하지 않음: {path}") from e
# 나쁨: Bare except (모든 예외를 뭉뚱그려 처리)
def load_config(path: str) -> Config:
try:
with open(path) as f:
return Config.from_json(f.read())
except:
return None # 조용한 실패!
def process_data(data: str) -> Result:
try:
parsed = json.loads(data)
except json.JSONDecodeError as e:
# 트레이스백을 보존하기 위해 예외를 체이닝함
raise ValueError(f"데이터 파싱 실패: {data}") from e
class AppError(Exception):
"""모든 애플리케이션 오류의 기본 예외."""
pass
class ValidationError(AppError):
"""입력 검증 실패 시 발생."""
pass
class NotFoundError(AppError):
"""요청한 리소스를 찾을 수 없을 때 발생."""
pass
# 사용 예시
def get_user(user_id: str) -> User:
user = db.find_user(user_id)
if not user:
raise NotFoundError(f"사용자를 찾을 수 없음: {user_id}")
return user
# 좋음: 컨텍스트 관리자 사용
def process_file(path: str) -> str:
with open(path, 'r') as f:
return f.read()
# 나쁨: 수동 리소스 관리
def process_file(path: str) -> str:
f = open(path, 'r')
try:
return f.read()
finally:
f.close()
from contextlib import contextmanager
@contextmanager
def timer(name: str):
"""코드 블록의 실행 시간을 측정하는 컨텍스트 관리자."""
start = time.perf_counter()
yield
elapsed = time.perf_counter() - start
print(f"{name} 소요 시간: {elapsed:.4f}초")
# 사용 예시
with timer("데이터 처리"):
process_large_dataset()
class DatabaseTransaction:
def __init__(self, connection):
self.connection = connection
def __enter__(self):
self.connection.begin_transaction()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.connection.commit()
else:
self.connection.rollback()
return False # 예외를 억제하지 않음
# 좋음: 간단한 변환을 위한 리스트 컴프리헨션
names = [user.name for user in users if user.is_active]
# 나쁨: 수동 루프
names = []
for user in users:
if user.is_active:
names.append(user.name)
# 복잡한 컴프리헨션은 풀어서 써야 함
# 나쁨: 너무 복잡함
result = [x * 2 for x in items if x > 0 if x % 2 == 0]
# 좋음: 제너레이터 함수 사용
def filter_and_transform(items: Iterable[int]) -> list[int]:
result = []
for x in items:
if x > 0 and x % 2 == 0:
result.append(x * 2)
return result
# 좋음: 지연 평가를 위한 제너레이터
total = sum(x * x for x in range(1_000_000))
# 나쁨: 큰 중간 리스트 생성
total = sum([x * x for x in range(1_000_000)])
def read_large_file(path: str) -> Iterator[str]:
"""큰 파일을 한 줄씩 읽습니다."""
with open(path) as f:
for line in f:
yield line.strip()
# 사용 예시
for line in read_large_file("huge.txt"):
process(line)
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class User:
"""__init__, __repr__, __eq__가 자동으로 생성되는 사용자 엔티티."""
id: str
name: str
email: str
created_at: datetime = field(default_factory=datetime.now)
is_active: bool = True
@dataclass
class User:
email: str
age: int
def __post_init__(self):
# 이메일 형식 검증
if "@" not in self.email:
raise ValueError(f"유효하지 않은 이메일: {self.email}")
# 나이 범위 검증
if self.age < 0 or self.age > 150:
raise ValueError(f"유효하지 않은 나이: {self.age}")
from typing import NamedTuple
class Point(NamedTuple):
"""불변 2D 좌표."""
x: float
y: float
def distance(self, other: 'Point') -> float:
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
import functools
import time
def timer(func: Callable) -> Callable:
"""함수 실행 시간을 측정하는 데코레이터."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} 소요 시간: {elapsed:.4f}초")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
def repeat(times: int):
"""함수를 여러 번 반복하는 데코레이터."""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(times=3)
def greet(name: str) -> str:
return f"Hello, {name}!"
import concurrent.futures
import threading
def fetch_url(url: str) -> str:
"""URL 호출 (I/O 바운드 작업)."""
import urllib.request
with urllib.request.urlopen(url) as response:
return response.read().decode()
def fetch_all_urls(urls: list[str]) -> dict[str, str]:
"""스레드를 사용하여 여러 URL을 동시에 호출합니다."""
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
future_to_url = {executor.submit(fetch_url, url): url for url in urls}
results = {}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
results[url] = future.result()
except Exception as e:
results[url] = f"오류: {e}"
return results
def process_data(data: list[int]) -> int:
"""CPU 집약적인 계산."""
return sum(x ** 2 for x in data)
def process_all(datasets: list[list[int]]) -> list[int]:
"""여러 프로세스를 사용하여 여러 데이터셋을 처리합니다."""
with concurrent.futures.ProcessPoolExecutor() as executor:
results = list(executor.map(process_data, datasets))
return results
import asyncio
async def fetch_async(url: str) -> str:
"""비동기적으로 URL을 호출합니다."""
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def fetch_all(urls: list[str]) -> dict[str, str]:
"""여러 URL을 동시에 호출합니다."""
tasks = [fetch_async(url) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return dict(zip(urls, results))
myproject/
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── main.py
│ ├── api/
│ │ ├── __init__.py
│ │ └── routes.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_api.py
│ └── test_models.py
├── pyproject.toml
├── README.md
└── .gitignore
# 좋음: 임포트 순서 - 표준 라이브러리, 서드파티, 로컬
import os
import sys
from pathlib import Path
import requests
from fastapi import FastAPI
from mypackage.models import User
from mypackage.utils import format_name
# 좋음: isort를 사용하여 임포트 자동 정렬
# pip install isort
# mypackage/__init__.py
"""mypackage - 샘플 Python 패키지."""
__version__ = "1.0.0"
# 패키지 수준에서 주요 클래스/함수 노출
from mypackage.models import User, Post
from mypackage.utils import format_name
__all__ = ["User", "Post", "format_name"]
# 나쁨: 일반 클래스는 __dict__를 사용함 (메모리 더 많이 소모)
class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
# 좋음: __slots__는 메모리 사용량을 줄임
class Point:
__slots__ = ['x', 'y']
def __init__(self, x: float, y: float):
self.x = x
self.y = y
# 나쁨: 전체 목록을 메모리에 반환함
def read_lines(path: str) -> list[str]:
with open(path) as f:
return [line.strip() for line in f]
# 좋음: 한 번에 한 줄씩 생성함
def read_lines(path: str) -> Iterator[str]:
with open(path) as f:
for line in f:
yield line.strip()
# 나쁨: 문자열 불변성으로 인해 O(n²) 발생
result = ""
for item in items:
result += str(item)
# 좋음: join을 사용하여 O(n) 달성
result = "".join(str(item) for item in items)
# 좋음: 대량 결합 시 StringIO 사용
from io import StringIO
buffer = StringIO()
for item in items:
buffer.write(str(item))
result = buffer.getvalue()
# 코드 포맷팅
black .
isort .
# 린팅
ruff check .
pylint mypackage/
# 타입 체크
mypy .
# 테스트
pytest --cov=mypackage --cov-report=html
# 보안 스캔
bandit -r .
# 의존성 관리
pip-audit
safety check
... (중략) ...
| 관용구 | 설명 |
|---|---|
| EAFP | 허락을 구하는 것보다 용서를 구하는 것이 쉽다 |
| 컨텍스트 관리자 | 리소스 관리를 위해 with 사용 |
| 리스트 컴프리헨션 | 간단한 데이터 변환용 |
| 제너레이터 | 지연 평가 및 대용량 데이터셋용 |
| 타입 힌트 | 함수 시그니처에 어노테이션 추가 |
| 데이터 클래스 | 자동 생성 메서드가 있는 데이터 컨테이너용 |
__slots__ | 메모리 최적화용 |
| f-strings | 문자열 포맷팅 (Python 3.6+) |
pathlib.Path | 경로 작업용 (Python 3.4+) |
enumerate | 루프에서 인덱스-요소 쌍 획득용 |
# 나쁨: 가변 객체를 기본 인수로 사용
def append_to(item, items=[]):
items.append(item)
return items
# 좋음: None을 사용하고 새 리스트 생성
def append_to(item, items=None):
if items is None:
items = []
items.append(item)
return items
# 나쁨: type()으로 타입 확인
if type(obj) == list:
process(obj)
# 좋음: isinstance 사용
if isinstance(obj, list):
process(obj)
# 나쁨: ==로 None 비교
if value == None:
process()
# 좋음: is 사용
if value is None:
process()
# 나쁨: from module import *
from os.path import *
# 좋음: 명시적 임포트
from os.path import join, exists
# 나쁨: Bare except
try:
risky_operation()
except:
pass
# 좋음: 특정 예외 명시
try:
risky_operation()
except SpecificError as e:
logger.error(f"작업 실패: {e}")
기억하십시오: Python 코드는 읽기 쉽고, 명시적이어야 하며, 최소 놀람의 원칙(Principle of Least Surprise)을 따라야 합니다. 의심스러울 때는 영리함보다 명확함을 우선시하십시오.