Giao diện
Thực hành: Decorator Challenges
🎯 Mục tiêu
🎯 Sau bài thực hành này, bạn sẽ:
- Viết decorator đo thời gian thực thi hàm
- Xây dựng retry decorator với exponential backoff
- Tạo decorator có tham số (decorator factory)
Yêu cầu
Bài 1: Timing Decorator
Viết decorator @timer đo thời gian thực thi của hàm.
python
import time
from functools import wraps
def timer(func):
"""Decorator đo thời gian chạy của hàm."""
# TODO: Implement decorator
# In ra: "⏱️ {func_name} chạy trong {elapsed:.4f}s"
pass
@timer
def slow_function():
time.sleep(0.5)
return "Xong!"
# Test
result = slow_function()
# Output: ⏱️ slow_function chạy trong 0.50xxsBài 2: Retry Decorator
Viết decorator @retry tự động thử lại khi hàm raise exception.
python
def retry(max_attempts: int = 3, delay: float = 1.0):
"""Decorator factory — retry với delay giữa các lần thử."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# TODO: Implement retry logic
# - Thử tối đa max_attempts lần
# - Chờ delay giây giữa mỗi lần
# - Raise exception cuối cùng nếu vẫn thất bại
pass
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def unstable_api_call():
import random
if random.random() < 0.7:
raise ConnectionError("Server không phản hồi")
return {"status": "ok"}Bài 3: Cache Decorator
Viết decorator @simple_cache tương tự functools.lru_cache nhưng đơn giản hơn.
python
def simple_cache(func):
"""Cache kết quả dựa trên arguments."""
cache = {}
@wraps(func)
def wrapper(*args):
# TODO: Check cache, return cached result hoặc compute mới
pass
wrapper.cache_info = lambda: f"Cache size: {len(cache)}"
wrapper.cache_clear = lambda: cache.clear()
return wrapper
@simple_cache
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# Test
print(fibonacci(30)) # Nhanh nhờ cache
print(fibonacci.cache_info()) # Cache size: 31Bài 4: Decorator với Arguments
Viết decorator @validate_types kiểm tra type của arguments.
python
def validate_types(**expected_types):
"""Kiểm tra type arguments trước khi gọi hàm."""
# TODO: Implement decorator factory
pass
@validate_types(name=str, age=int)
def create_user(name: str, age: int) -> dict:
return {"name": name, "age": age}
create_user("Alice", 30) # OK
create_user("Alice", "30") # Raises TypeErrorGợi ý
Gợi ý Bài 1
- Dùng
time.perf_counter()thay vìtime.time()để đo chính xác hơn - Nhớ dùng
@wraps(func)để bảo toàn metadata của hàm gốc - Return kết quả của hàm gốc, không chỉ in thời gian
Gợi ý Bài 2
- Decorator factory = hàm trả về decorator, cần 3 lớp nested
- Dùng
time.sleep(delay)giữa các lần retry - Lưu exception cuối cùng trong biến
last_exceptionđể raise
Gợi ý Bài 3
- Dùng
argslàm cache key (tuple tự hashable) if args in cache: return cache[args]- Thêm
cache_info()vàcache_clear()như attribute của wrapper
Lời giải tham khảo
Xem lời giải (Bài 1 & 2 — Bài 3, 4 hãy tự thử từ gợi ý!)
python
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"⏱️ {func.__name__} chạy trong {elapsed:.4f}s")
return result
return wrapper
def retry(max_attempts: int = 3, delay: float = 1.0):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"⚠️ Lần {attempt}/{max_attempts} thất bại: {e}")
if attempt < max_attempts:
time.sleep(delay)
raise last_exception
return wrapper
return decorator