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
- Decorators wrap functions to add behavior.
- Syntax:
@decorator_nameabovedef. - Parameters: use a decorator factory that returns the real decorator.
- wraps: preserve
__name__,__doc__, and the function module. - Uses: logging, caching, authentication, retries, timing.
Next steps
- Generators (
yield) - Flask web basics
Related posts
- Python functions | Parameters, return values, lambdas, decorators
- Python environment setup | Install Python on Windows and Mac
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.
Related Articles (Internal Links)
Other articles related to this topic.
- Python Functions | Parameters· Return Values
- Python Classes | Object-Oriented Programming (OOP) Explained
- TypeScript Decorators
- JavaScript Classes | ES6 Class Syntax Explained
Keywords Covered in This Article (Related Search Terms)
This article covers Python, Decorator, functools, Higher-Order Functions, Metaprogramming.