본문으로 건너뛰기 Python Decorators | @decorator Syntax· functools.wraps

Python Decorators | @decorator Syntax· functools.wraps

Python Decorators | @decorator Syntax· functools.wraps

이 글의 핵심

Master Python decorators: function decorators, parameterized factories, logging, caching, auth, class decorators, and functools.wraps—with clear examples.

Introduction

“Dressing up functions”

Decorators are a powerful Python feature for adding behavior around functions (or classes).

1. Function decorators basics

A simple function decorator

def timer(func):
    """Measure how long a function runs."""
    import time
    
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f}s")
        return result
    
    return wrapper
@timer
def slow_function():
    import time
    time.sleep(1)
    return "done"
result = slow_function()
# slow_function took: 1.0012s

Logging decorator

def logger(func):
    """Log function calls."""
    def wrapper(*args, **kwargs):
        print(f"[call] {func.__name__}({args}, {kwargs})")
        result = func(*args, **kwargs)
        print(f"[return] {result}")
        return result
    return wrapper
@logger
def add(a, b):
    return a + b
add(3, 5)
# [call] add((3, 5), {})
# [return] 8

2. Decorators with arguments

Decorator factory

def repeat(times):
    """Run the wrapped function multiple times."""
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator
@repeat(3)
def greet(name):
    return f"Hello, {name}!"
print(greet("Alice"))
# ['Hello, Alice!', 'Hello, Alice!', 'Hello, Alice!']

3. Practical decorators

Memoization (caching)

def memoize(func):
    """Cache function results."""
    cache = {}
    
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    
    return wrapper
@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100))  # very fast!

Authentication decorator

def require_auth(func):
    """Require an authenticated user."""
    def wrapper(user, *args, **kwargs):
        if not user.get('is_authenticated'):
            raise PermissionError("Login required")
        return func(user, *args, **kwargs)
    return wrapper
@require_auth
def delete_post(user, post_id):
    return f"Post {post_id} deleted"
# Usage
user = {'name': 'Alice', 'is_authenticated': True}
print(delete_post(user, 123))  # Post 123 deleted
guest = {'name': 'guest', 'is_authenticated': False}
# delete_post(guest, 123)  # PermissionError!

4. Class decorators

def singleton(cls):
    """Singleton pattern."""
    instances = {}
    
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    
    return get_instance
@singleton
class Database:
    def __init__(self):
        print("Database connection")
        self.connection = "Connected"
# Usage
db1 = Database()  # Database connection
db2 = Database()  # no extra print (same instance)
print(db1 is db2)  # True

5. functools.wraps

Preserve metadata

from functools import wraps
def my_decorator(func):
    @wraps(func)  # keep original function metadata
    def wrapper(*args, **kwargs):
        """Wrapper docstring."""
        return func(*args, **kwargs)
    return wrapper
@my_decorator
def greet(name):
    """Greeting function."""
    return f"Hello, {name}!"
print(greet.__name__)  # greet (without wraps you'd see wrapper)
print(greet.__doc__)   # Greeting function.

6. Real-world example: API retry decorator

import time
from functools import wraps
def retry(max_attempts=3, delay=1):
    """Retry on failure."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f"Attempt {attempt + 1} failed: {e}")
                    time.sleep(delay)
        return wrapper
    return decorator
@retry(max_attempts=3, delay=0.5)
def fetch_data(url):
    import random
    if random.random() < 0.7:
        raise ConnectionError("connection failed")
    return f"data from {url}"

Practical tips

Decorator patterns

# ✅ Stacking multiple decorators
@timer
@logger
@retry(3)
def important_function():
    pass
# Execution order: retry → logger → timer → underlying function
# ✅ Always prefer functools.wraps
from functools import wraps
def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

Summary

Key takeaways

  1. Decorators wrap functions to add behavior.
  2. Syntax: @decorator_name above def.
  3. Parameters: use a decorator factory that returns the real decorator.
  4. wraps: preserve __name__, __doc__, and the function module.
  5. Uses: logging, caching, authentication, retries, timing.

Next steps



Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Master Python decorators: function decorators, parameterized factories, logging, caching, auth, class decorators, and fu…

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the Python series index for the full picture.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers Python, Decorator, functools, Higher-Order Functions, Metaprogramming.