Skip to content

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.50xxs

Bà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: 31

Bà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 TypeError

Gợ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 args làm cache key (tuple tự hashable)
  • if args in cache: return cache[args]
  • Thêm cache_info()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