[Python 100일 챌린지] Day 61 - 데코레이터 기초
데코레이터는 파이썬의 슈퍼파워예요! 🦸♂️
지금까지 만든 함수들, 일일이 로그 찍고 시간 재고 했죠? 데코레이터를 쓰면 단 한 줄(
@timer)로 모든 함수에 자동 적용 가능해요! Flask, Django 같은 웹 프레임워크도 데코레이터 투성이랍니다. 😊Phase 3에서 배운 함수가 기초라면, 오늘은 “함수를 다루는 함수”를 배워요. 처음엔 조금 어려울 수 있지만, 한 번 이해하면 코드가 훨씬 깔끔해집니다! 💪
(30분 완독 ⭐⭐⭐)
🎯 오늘의 학습 목표
📚 사전 지식
- Day 27: 함수 정의하기 - 함수와 클로저 개념
- Day 31: 클래스 기초 - 객체지향 프로그래밍
🎯 학습 목표 1: 데코레이터의 개념 이해하기
한 줄 설명
데코레이터 = 함수에 기능을 덧붙이는 포장지 📦
선물 상자에 리본 붙이는 것처럼, 함수에 기능을 덧붙여요!
1.1 데코레이터가 필요한 이유
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 상황: 여러 함수의 실행 시간을 측정하고 싶어요
# ❌ 나쁜 방법: 모든 함수에 일일이 코드 추가
def calculate1():
start = time.time()
# 원래 코드...
result = sum(range(1000000))
print(f"시간: {time.time() - start}초")
return result
def calculate2():
start = time.time()
# 원래 코드...
result = sum(range(2000000))
print(f"시간: {time.time() - start}초")
return result
# ✅ 좋은 방법: 데코레이터 한 번만 작성!
@timer # 이 한 줄만 추가하면 끝!
def calculate1():
return sum(range(1000000))
@timer # 이 한 줄만 추가하면 끝!
def calculate2():
return sum(range(2000000))
💡 실생활 비유: 스마트폰 케이스 같아요! 핸드폰(함수)을 바꾸지 않고, 케이스(데코레이터)만 씌워서 기능을 추가하죠.
1.2 함수는 일급 객체(First-Class Object)
파이썬에서는 함수를 변수처럼 다룰 수 있어요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 함수를 변수에 담기
def greet(name):
return f"Hello, {name}!"
say_hi = greet # 함수를 변수에 저장!
print(say_hi("Alice")) # Hello, Alice!
# 함수를 인자로 전달
def call_func(func, name):
return func(name)
result = call_func(greet, "Bob")
print(result) # Hello, Bob!
# 함수가 함수를 반환
def get_greeter():
def greet(name):
return f"Hi, {name}!"
return greet # 함수를 리턴!
greeter = get_greeter()
print(greeter("Charlie")) # Hi, Charlie!
💡 핵심: 함수를 값처럼 다룰 수 있어야 데코레이터를 이해할 수 있어요!
1.3 클로저(Closure) 복습
데코레이터는 클로저를 활용해요.
1
2
3
4
5
6
7
8
9
10
def outer(x):
"""외부 함수"""
def inner(y):
"""내부 함수 - outer의 x를 기억해요!"""
return x + y
return inner
add_5 = outer(5) # x=5를 기억하는 함수 생성
print(add_5(3)) # 8 (5 + 3)
print(add_5(10)) # 15 (5 + 10)
💡 클로저: 함수가 만들어질 때의 환경(변수)을 기억하는 함수예요!
🎯 학습 목표 2: 함수 데코레이터 작성하기
2.1 기본 데코레이터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def my_decorator(func):
"""가장 기본적인 데코레이터"""
def wrapper():
print("🎬 함수 실행 전")
func()
print("✅ 함수 실행 후")
return wrapper
# 데코레이터 적용 방법 1: 수동으로
def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello) # 포장하기
say_hello()
# 출력:
# 🎬 함수 실행 전
# Hello!
# ✅ 함수 실행 후
# 데코레이터 적용 방법 2: @ 문법 (권장!) ⭐
@my_decorator
def say_goodbye():
print("Goodbye!")
say_goodbye()
# 출력:
# 🎬 함수 실행 전
# Goodbye!
# ✅ 함수 실행 후
💡
@문법:@my_decorator는func = my_decorator(func)와 같아요!
2.2 매개변수가 있는 함수 데코레이팅
실제 함수들은 대부분 매개변수가 있죠? 이렇게 해요:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def my_decorator(func):
def wrapper(*args, **kwargs): # 모든 인자를 받아요!
print(f"📞 함수 호출: {func.__name__}")
print(f"📝 인자: {args}, {kwargs}")
result = func(*args, **kwargs) # 원본 함수 실행
print(f"💡 결과: {result}")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
@my_decorator
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# 사용해보기
add(3, 5)
# 출력:
# 📞 함수 호출: add
# 📝 인자: (3, 5), {}
# 💡 결과: 8
greet("Alice", greeting="Hi")
# 출력:
# 📞 함수 호출: greet
# 📝 인자: ('Alice',), {'greeting': 'Hi'}
# 💡 결과: Hi, Alice!
💡
*args, **kwargs: 어떤 인자든 받을 수 있는 만능 패턴이에요!
2.3 실전: 실행 시간 측정 데코레이터
진짜 유용한 예제예요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import time
def timer(func):
"""함수 실행 시간을 측정하는 데코레이터 ⏱️"""
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"⏱️ {func.__name__} 실행 시간: {end_time - start_time:.4f}초")
return result
return wrapper
@timer
def slow_function():
"""시간이 걸리는 함수"""
time.sleep(1)
return "완료!"
@timer
def calculate_sum(n):
"""1부터 n까지의 합"""
return sum(range(1, n + 1))
# 사용
slow_function()
# 출력: ⏱️ slow_function 실행 시간: 1.0012초
result = calculate_sum(1000000)
print(f"결과: {result}")
# 출력:
# ⏱️ calculate_sum 실행 시간: 0.0234초
# 결과: 500000500000
🎯 학습 목표 3: functools.wraps 사용하기
3.1 데코레이터의 문제점
1
2
3
4
5
6
7
8
9
10
11
12
13
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def greet(name):
"""인사를 출력하는 함수"""
return f"Hello, {name}!"
# 문제 발생! ❌
print(greet.__name__) # wrapper (원래는 greet여야 해요)
print(greet.__doc__) # None (docstring이 사라졌어요!)
💡 문제: 데코레이터가 함수의 정보(이름, 설명)를 덮어써버려요!
3.2 functools.wraps로 해결
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from functools import wraps
def my_decorator(func):
@wraps(func) # 이 한 줄이면 해결! ✨
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def greet(name):
"""인사를 출력하는 함수"""
return f"Hello, {name}!"
# 해결! ✅
print(greet.__name__) # greet (정상!)
print(greet.__doc__) # 인사를 출력하는 함수 (정상!)
3.3 올바른 데코레이터 템플릿 📝
앞으로 데코레이터 만들 때는 항상 이 템플릿을 사용하세요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from functools import wraps
def my_decorator(func):
"""데코레이터 설명"""
@wraps(func) # 필수!
def wrapper(*args, **kwargs):
# 함수 실행 전 작업
print(f"[Before] {func.__name__} 호출")
# 원본 함수 실행
result = func(*args, **kwargs)
# 함수 실행 후 작업
print(f"[After] {func.__name__} 완료")
return result
return wrapper
🎯 학습 목표 4: 실전 데코레이터 예제
예제 1: 로깅 데코레이터 📝
모든 함수 호출을 기록하고 싶을 때 사용해요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from functools import wraps
import logging
logging.basicConfig(level=logging.INFO)
def log_function_call(func):
"""함수 호출을 로깅하는 데코레이터"""
@wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"📞 함수 '{func.__name__}' 호출됨")
logging.info(f" 📝 인자: {args}, {kwargs}")
try:
result = func(*args, **kwargs)
logging.info(f" ✅ 결과: {result}")
return result
except Exception as e:
logging.error(f" ❌ 오류 발생: {e}")
raise
return wrapper
@log_function_call
def divide(a, b):
"""나눗셈을 수행하는 함수"""
return a / b
# 사용
divide(10, 2)
# INFO:root:📞 함수 'divide' 호출됨
# INFO:root: 📝 인자: (10, 2), {}
# INFO:root: ✅ 결과: 5.0
divide(10, 0)
# INFO:root:📞 함수 'divide' 호출됨
# INFO:root: 📝 인자: (10, 0), {}
# ERROR:root: ❌ 오류 발생: division by zero
예제 2: 재시도 데코레이터 🔄
네트워크 요청처럼 실패할 수 있는 작업에 유용해요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from functools import wraps
import time
def retry(max_attempts=3, delay=1):
"""실패 시 재시도하는 데코레이터"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts >= max_attempts:
print(f"❌ {max_attempts}번 시도 후 실패")
raise
print(f"⚠️ 시도 {attempts} 실패: {e}")
print(f" {delay}초 후 재시도...")
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=1)
def unreliable_function():
"""가끔 실패하는 함수 (API 호출 등)"""
import random
if random.random() < 0.7:
raise Exception("일시적 오류 발생")
return "성공!"
# 사용
result = unreliable_function()
print(f"✅ 결과: {result}")
예제 3: 캐시 데코레이터 💾
한 번 계산한 결과를 저장해서 재사용해요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from functools import wraps
def memoize(func):
"""함수 결과를 캐싱하는 데코레이터"""
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
print(f"💾 캐시에서 가져옴: {args}")
return cache[args]
print(f"🔄 계산 중: {args}")
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def fibonacci(n):
"""피보나치 수를 계산하는 함수"""
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# 사용
print(fibonacci(5))
# 출력:
# 🔄 계산 중: (5,)
# 🔄 계산 중: (4,)
# 🔄 계산 중: (3,)
# 🔄 계산 중: (2,)
# 🔄 계산 중: (1,)
# 🔄 계산 중: (0,)
# 💾 캐시에서 가져옴: (1,)
# 💾 캐시에서 가져옴: (2,)
# 💾 캐시에서 가져옴: (3,)
# 5
💡 효과: 피보나치(100)을 계산할 때, 캐시 없으면 몇 분 걸리는데, 캐시 있으면 1초도 안 걸려요!
예제 4: 권한 확인 데코레이터 🔐
웹 애플리케이션에서 자주 쓰는 패턴이에요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from functools import wraps
# 현재 사용자 (실제로는 세션이나 DB에서 가져와요)
current_user = {"username": "alice", "role": "admin"}
def require_role(role):
"""특정 역할을 요구하는 데코레이터"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if current_user.get("role") != role:
raise PermissionError(
f"❌ 이 함수는 '{role}' 권한이 필요해요. "
f"현재 권한: {current_user.get('role')}"
)
return func(*args, **kwargs)
return wrapper
return decorator
@require_role("admin")
def delete_user(user_id):
"""사용자를 삭제하는 함수 (관리자 전용)"""
return f"✅ 사용자 {user_id} 삭제됨"
@require_role("user")
def view_profile():
"""프로필 보기 (일반 사용자)"""
return "프로필 정보"
# 사용
print(delete_user(123)) # ✅ 성공 (admin 권한)
# 권한 변경
current_user["role"] = "user"
print(delete_user(123)) # ❌ PermissionError 발생
💡 오늘의 핵심 요약
- 데코레이터 = 함수에 기능을 추가하는 포장지 📦
- 원본 함수를 수정하지 않고 기능 추가
- 기본 구조 (템플릿):
1 2 3 4 5 6 7 8 9 10
from functools import wraps def my_decorator(func): @wraps(func) # 필수! def wrapper(*args, **kwargs): # 전처리 result = func(*args, **kwargs) # 후처리 return result return wrapper
- 실전 활용:
- 🔍 로깅: 함수 호출 기록
- ⏱️ 타이머: 실행 시간 측정
- 💾 캐싱: 결과 저장
- 🔐 권한 확인: 접근 제어
- 꼭 기억하세요:
- 항상
@wraps(func)사용해요! *args, **kwargs로 모든 인자 처리해요!
- 항상
🧪 연습 문제
문제 1: 디버그 데코레이터
함수의 입력과 출력을 보기 쉽게 출력하는 debug 데코레이터를 작성하세요.
1
2
3
4
5
6
7
8
9
@debug
def add(a, b):
return a + b
add(3, 5)
# 출력:
# 🔍 함수: add
# 📥 입력: a=3, b=5
# 📤 출력: 8
💡 힌트
단계별 힌트:
from functools import wraps임포트inspect.signature()로 함수 시그니처 가져오기- 입력 인자를 예쁘게 출력하기
- 함수 실행 후 결과 출력하기
체크 포인트:
- ✅
@wraps(func)사용했나요? - ✅
*args, **kwargs처리했나요? - ✅ 이모지로 보기 좋게 꾸몄나요? 😊
✅ 정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from functools import wraps
import inspect
def debug(func):
"""함수의 입력과 출력을 출력하는 데코레이터"""
@wraps(func)
def wrapper(*args, **kwargs):
# 함수 시그니처 가져오기
sig = inspect.signature(func)
bound_args = sig.bind(*args, **kwargs)
bound_args.apply_defaults()
print(f"🔍 함수: {func.__name__}")
print("📥 입력:", ", ".join(
f"{k}={v}" for k, v in bound_args.arguments.items()
))
result = func(*args, **kwargs)
print(f"📤 출력: {result}")
return result
return wrapper
# 테스트
@debug
def add(a, b):
return a + b
@debug
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
add(3, 5)
greet("Alice")
문제 2: 호출 횟수 제한 데코레이터
함수를 최대 N번만 호출할 수 있도록 제한하는 데코레이터를 작성하세요.
💡 힌트
단계별 힌트:
count변수로 호출 횟수 추적nonlocal count로 외부 변수 수정count >= max_calls면 예외 발생- 매번
count증가
✅ 정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from functools import wraps
def limit_calls(max_calls):
"""함수 호출 횟수를 제한하는 데코레이터"""
def decorator(func):
count = 0
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal count
if count >= max_calls:
raise Exception(
f"❌ {func.__name__}는 최대 {max_calls}번만 호출 가능해요!"
)
count += 1
print(f"📞 호출 횟수: {count}/{max_calls}")
return func(*args, **kwargs)
return wrapper
return decorator
# 테스트
@limit_calls(3)
def say_hello():
print("Hello!")
say_hello() # 호출 1/3
say_hello() # 호출 2/3
say_hello() # 호출 3/3
say_hello() # Exception 발생!
📚 이전 학습
Day 60: Phase 6 실전 프로젝트 - 웹 크롤러 ⭐⭐⭐
어제는 Phase 6 마무리 프로젝트로 웹 크롤러를 만들었어요!
📚 다음 학습
Day 62: 데코레이터 심화 ⭐⭐⭐
내일은 매개변수가 있는 데코레이터, 클래스 데코레이터, @property 등 고급 기법을 배워요!
“고급 개념도 차근차근 배우면 어렵지 않아요!” 🚀
Day 61/100 Phase 7: 고급 파이썬 개념 #100DaysOfPython
