코드 최적화
알고리즘 선택, 내장 함수 활용, 데이터 구조 최적화로 Python 코드를 빠르게 만듭니다.
내장 함수와 라이브러리 우선 사용
import timeit
data = list(range(100_000))
# ❌ 느린 방법 — Python 루프
def manual_sum(lst):
total = 0
for x in lst:
total += x
return total
# ✅ 빠른 방법 — C로 구현된 내장 함수
total = sum(data)
# 비교
print(timeit.timeit(lambda: manual_sum(data), number=100)) # ~1.5s
print(timeit.timeit(lambda: sum(data), number=100)) # ~0.2s
# 자주 쓰는 최적화 패턴
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# map / filter (C 레벨 반복)
squares = list(map(lambda x: x ** 2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
# sorted + key
words = ["banana", "apple", "cherry", "date"]
by_length = sorted(words, key=len)
by_last = sorted(words, key=lambda w: w[-1])
# any / all (단락 평가)
has_negative = any(x < 0 for x in numbers)
all_positive = all(x > 0 for x in numbers)
# max / min with key
longest = max(words, key=len)
리스트 컴프리헨션 vs 제너레이터
import sys
# 리스트 컴프리헨션 — 전체 결과를 메모리에 저장
squares_list = [x ** 2 for x in range(1_000_000)]
print(sys.getsizeof(squares_list)) # ~8MB
# 제너레이터 — 지연 평가, 메모리 효율적
squares_gen = (x ** 2 for x in range(1_000_000))
print(sys.getsizeof(squares_gen)) # ~120 bytes
# 제너레이터 함수
def chunked(lst: list, size: int):
"""리스트를 size 단위로 분할"""
for i in range(0, len(lst), size):
yield lst[i:i + size]
for chunk in chunked(list(range(100)), 10):
print(chunk) # 10개씩 처리
# itertools 활용
import itertools
# chain — 여러 이터러블 연결 (복사 없음)
combined = list(itertools.chain([1, 2], [3, 4], [5, 6]))
# islice — 슬라이싱 (제너레이터 지원)
first_5 = list(itertools.islice(squares_gen, 5))
# groupby — 연속된 그룹
data = [("A", 1), ("A", 2), ("B", 3), ("B", 4), ("A", 5)]
for key, group in itertools.groupby(data, key=lambda x: x[0]):
print(key, list(group))
자료구조 선택
import timeit
from collections import deque, defaultdict, Counter, OrderedDict
# ── list vs deque ─────────────────────────────────────────
# list: 앞쪽 삽입/삭제 O(n)
lst = list(range(100_000))
t1 = timeit.timeit(lambda: lst.insert(0, -1), number=1000)
# deque: 양쪽 삽입/삭제 O(1)
dq = deque(range(100_000))
t2 = timeit.timeit(lambda: dq.appendleft(-1), number=1000)
print(f"list insert: {t1:.4f}s | deque appendleft: {t2:.4f}s")
# ── dict vs set for membership ────────────────────────────
big_list = list(range(100_000))
big_set = set(big_list)
t1 = timeit.timeit(lambda: 99999 in big_list, number=1000) # O(n)
t2 = timeit.timeit(lambda: 99999 in big_set, number=1000) # O(1)
print(f"list in: {t1:.6f}s | set in: {t2:.6f}s")
# ── defaultdict ───────────────────────────────────────────
# ❌ 느린 방법
word_count = {}
words = "the quick brown fox jumps over the lazy dog".split()
for word in words:
if word not in word_count:
word_count[word] = 0
word_count[word] += 1
# ✅ defaultdict
word_count = defaultdict(int)
for word in words:
word_count[word] += 1
# ✅ Counter (가장 간결)
word_count = Counter(words)
top3 = word_count.most_common(3)
# ── heapq — 우선순위 큐 ───────────────────────────────────
import heapq
data = [5, 3, 8, 1, 9, 2, 7]
heapq.heapify(data) # O(n)
smallest = heapq.heappop(data) # O(log n)
heapq.heappush(data, 4) # O(log n)
top_3 = heapq.nlargest(3, data) # 상위 3개
low_3 = heapq.nsmallest(3, data) # 하위 3개
문자열 최적화
import timeit
parts = [f"item_{i}" for i in range(10_000)]
# ❌ + 연산 — O(n²) 메모리 복사
def concat_plus(parts):
result = ""
for part in parts:
result += part
return result
# ✅ join — O(n) 단일 할당
def concat_join(parts):
return "".join(parts)
t1 = timeit.timeit(lambda: concat_plus(parts), number=10)
t2 = timeit.timeit(lambda: concat_join(parts), number=10)
print(f"concat +: {t1:.4f}s | join: {t2:.4f}s")
# f-string vs format vs %
name, score = "Alice", 95.5
s1 = f"이름: {name}, 점수: {score:.1f}" # 가장 빠름
s2 = "이름: {}, 점수: {:.1f}".format(name, score)
s3 = "이름: %s, 점수: %.1f" % (name, score)
# 대용량 문자열 처리 — io.StringIO
import io
buffer = io.StringIO()
for i in range(10_000):
buffer.write(f"line {i}\n")
result = buffer.getvalue()
캐싱과 메모이제이션
from functools import lru_cache, cache
import timeit
# lru_cache — 최근 호출 결과 캐싱
@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# cache (Python 3.9+) — 크기 제한 없는 lru_cache
@cache
def expensive_computation(x: int, y: int) -> float:
import math
return math.sqrt(x ** 2 + y ** 2)
# 클래스 메서드 캐싱
from functools import cached_property
class Circle:
def __init__(self, radius: float):
self.radius = radius
@cached_property # 처음 접근 시 계산, 이후 캐시
def area(self) -> float:
import math
return math.pi * self.radius ** 2
@cached_property
def circumference(self) -> float:
import math
return 2 * math.pi * self.radius
c = Circle(5.0)
print(c.area) # 계산
print(c.area) # 캐시에서 반환
# TTL 캐시 (시간 기반 만료)
import time
from typing import Any
class TTLCache:
def __init__(self, ttl: float = 60.0):
self._cache: dict[str, tuple[Any, float]] = {}
self.ttl = ttl
def get(self, key: str) -> Any | None:
if key in self._cache:
value, expires_at = self._cache[key]
if time.monotonic() < expires_at:
return value
del self._cache[key]
return None
def set(self, key: str, value: Any) -> None:
self._cache[key] = (value, time.monotonic() + self.ttl)
루프 최적화
# ── 지역 변수 활용 ─────────────────────────────────────────
import math
# ❌ 전역 속성 반복 조회
def slow_math(data):
return [math.sqrt(x) for x in data]
# ✅ 지역 변수로 바인딩
def fast_math(data):
sqrt = math.sqrt # 속성 조회 1회
return [sqrt(x) for x in data]
# ── 조건부 처리 순서 ───────────────────────────────────────
# 빠른 조건(자주 True) → 앞에 배치 (단락 평가 활용)
def process(items):
return [
item for item in items
if item > 0 # 대부분 통과 → 앞에
and item % 2 == 0 # 일부 통과 → 뒤에
and is_prime(item) # 비싼 연산 → 맨 뒤
]
def is_prime(n):
if n < 2: return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0: return False
return True
# ── zip / enumerate ───────────────────────────────────────
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
# ❌ 인덱스 접근
for i in range(len(names)):
print(f"{names[i]}: {scores[i]}")
# ✅ zip
for name, score in zip(names, scores):
print(f"{name}: {score}")
# ✅ enumerate
for i, name in enumerate(names, start=1):
print(f"{i}. {name}")
정리
| 기법 | 개선 효과 | 적용 상황 |
|---|---|---|
| 내장 함수 우선 | 5~10x | 반복문 대체 가능할 때 |
| 제너레이터 | 메모리 절약 | 대용량 데이터 처리 |
| set/dict 조회 | O(n) → O(1) | 멤버십 테스트 |
"".join() | O(n²) → O(n) | 문자열 누적 |
lru_cache | 반복 연산 제거 | 순수 함수, 재귀 |
| 지역 변수 바인딩 | 5~15% | 타이트한 루프 |