Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2cf8b5d

Browse files
Implement code changes to enhance functionality and improve performance
1 parent 59b8e87 commit 2cf8b5d

File tree

2 files changed

+324
-0
lines changed

2 files changed

+324
-0
lines changed
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
---
2+
title: 'Python Decorators - Simple Patterns to Level Up Your Code - Python Cheatsheet'
3+
description: Decorators are one of Python's most elegant features, allowing you to modify the behavior of functions or methods in a clean and readable way.
4+
date: Aug 16, 2025
5+
updated: Aug 16, 2025
6+
tags: python, intermediate, beta
7+
socialImage: /blog/python-decorators.jpg
8+
---
9+
10+
<route lang="yaml">
11+
meta:
12+
layout: article
13+
title: 'Python Decorators - Simple Patterns to Level Up Your Code'
14+
description: Decorators are one of Python's most elegant features, allowing you to modify the behavior of functions or methods in a clean and readable way.
15+
date: Aug 16, 2025
16+
updated: Aug 16, 2025
17+
socialImage: /blog/python-decorators.jpg
18+
tags: python, intermediate, beta
19+
</route>
20+
21+
<blog-title-header :frontmatter="frontmatter" title="Python Decorators: Simple Patterns to Level Up Your Code" />
22+
23+
You know that feeling when you see `@something` above a function and wonder what black magic is happening? I've been there too. Decorators might look intimidating, but they're actually one of Python's most elegant features once you understand the basics.
24+
25+
Think of decorators as gift wrapping for your functions. The function inside stays the same, but the decorator adds a nice bow on top – extra functionality without changing the original code.
26+
27+
## The Simplest Decorator
28+
29+
Let's start with the most basic example to understand what's happening:
30+
31+
```python
32+
def my_decorator(func):
33+
def wrapper():
34+
print("Something happens before!")
35+
func()
36+
print("Something happens after!")
37+
return wrapper
38+
39+
@my_decorator
40+
def say_hello():
41+
print("Hello!")
42+
43+
say_hello()
44+
# Something happens before!
45+
# Hello!
46+
# Something happens after!
47+
```
48+
49+
That's it! A decorator is just a function that takes another function and wraps it with extra behavior. The `@my_decorator` syntax is just a cleaner way of writing `say_hello = my_decorator(say_hello)`.
50+
51+
## Your First Useful Decorator: Timer
52+
53+
Here's a decorator you'll actually want to use – one that tells you how long your functions take to run:
54+
55+
```python
56+
import time
57+
import functools
58+
59+
def timer(func):
60+
@functools.wraps(func) # Preserves the original function's name and docs
61+
def wrapper(*args, **kwargs):
62+
start = time.time()
63+
result = func(*args, **kwargs)
64+
end = time.time()
65+
print(f"{func.__name__} took {end - start:.4f} seconds")
66+
return result
67+
return wrapper
68+
69+
@timer
70+
def slow_function():
71+
time.sleep(1)
72+
return "Done!"
73+
74+
result = slow_function()
75+
# slow_function took 1.0041 seconds
76+
print(result) # Done!
77+
```
78+
79+
Notice how we use `*args` and `**kwargs`? This makes our decorator work with any function, regardless of how many arguments it takes.
80+
81+
## Debug Your Code: Logger Decorator
82+
83+
When you're trying to figure out what's going wrong, this decorator is incredibly handy:
84+
85+
```python
86+
def debug(func):
87+
@functools.wraps(func)
88+
def wrapper(*args, **kwargs):
89+
args_str = ', '.join(repr(arg) for arg in args)
90+
kwargs_str = ', '.join(f"{k}={v!r}" for k, v in kwargs.items())
91+
all_args = ', '.join(filter(None, [args_str, kwargs_str]))
92+
print(f"Calling {func.__name__}({all_args})")
93+
94+
result = func(*args, **kwargs)
95+
print(f"{func.__name__} returned {result!r}")
96+
return result
97+
return wrapper
98+
99+
@debug
100+
def add_numbers(a, b, multiply_by=1):
101+
return (a + b) * multiply_by
102+
103+
result = add_numbers(5, 3, multiply_by=2)
104+
# Calling add_numbers(5, 3, multiply_by=2)
105+
# add_numbers returned 16
106+
```
107+
108+
109+
## Control Access: Authentication Decorator
110+
111+
Want to make sure only certain users can run a function? Here's how:
112+
113+
```python
114+
def requires_auth(func):
115+
@functools.wraps(func)
116+
def wrapper(*args, **kwargs):
117+
# In a real app, you'd check actual authentication
118+
user_logged_in = True # This would come from your auth system
119+
120+
if not user_logged_in:
121+
return "Access denied! Please log in."
122+
123+
return func(*args, **kwargs)
124+
return wrapper
125+
126+
@requires_auth
127+
def delete_everything():
128+
return "💥 Everything deleted! (just kidding)"
129+
130+
result = delete_everything()
131+
print(result) # 💥 Everything deleted! (just kidding)
132+
```
133+
134+
135+
## Speed Things Up: Cache Decorator
136+
137+
If you have a function that does expensive calculations with the same inputs, cache the results:
138+
139+
```python
140+
def cache(func):
141+
cached_results = {}
142+
143+
@functools.wraps(func)
144+
def wrapper(*args):
145+
if args in cached_results:
146+
print(f"Cache hit for {func.__name__}{args}")
147+
return cached_results[args]
148+
149+
print(f"Computing {func.__name__}{args}")
150+
result = func(*args)
151+
cached_results[args] = result
152+
return result
153+
154+
return wrapper
155+
156+
@cache
157+
def fibonacci(n):
158+
if n < 2:
159+
return n
160+
return fibonacci(n - 1) + fibonacci(n - 2)
161+
162+
print(fibonacci(10))
163+
# Computing fibonacci(10)
164+
# Computing fibonacci(9)
165+
# Computing fibonacci(8)
166+
# ... (lots of computation)
167+
# Cache hit for fibonacci(2)
168+
# Cache hit for fibonacci(3)
169+
# ... (cache hits)
170+
# 55
171+
```
172+
173+
174+
## Retry Failed Operations
175+
176+
Sometimes functions fail due to network issues or temporary problems. This decorator automatically retries:
177+
178+
```python
179+
import random
180+
181+
def retry(max_attempts=3):
182+
def decorator(func):
183+
@functools.wraps(func)
184+
def wrapper(*args, **kwargs):
185+
for attempt in range(max_attempts):
186+
try:
187+
return func(*args, **kwargs)
188+
except Exception as e:
189+
print(f"Attempt {attempt + 1} failed: {e}")
190+
if attempt == max_attempts - 1:
191+
print("All attempts failed!")
192+
raise
193+
return None
194+
return wrapper
195+
return decorator
196+
197+
@retry(max_attempts=3)
198+
def unreliable_api_call():
199+
if random.random() < 0.7: # 70% chance of failure
200+
raise Exception("Network error")
201+
return "Success!"
202+
203+
# This will retry up to 3 times if it fails
204+
result = unreliable_api_call()
205+
```
206+
207+
208+
## Rate Limiting: Slow Down Your Code
209+
210+
Sometimes you need to be gentle with APIs or databases:
211+
212+
```python
213+
import time
214+
215+
def rate_limit(seconds):
216+
def decorator(func):
217+
last_called = [^0] # Use list to store mutable value
218+
219+
@functools.wraps(func)
220+
def wrapper(*args, **kwargs):
221+
elapsed = time.time() - last_called
222+
if elapsed < seconds:
223+
time.sleep(seconds - elapsed)
224+
225+
last_called = time.time()
226+
return func(*args, **kwargs)
227+
228+
return wrapper
229+
return decorator
230+
231+
@rate_limit(1) # At most once per second
232+
def call_api():
233+
print(f"API called at {time.time():.2f}")
234+
235+
# These will be spaced out by 1 second each
236+
call_api()
237+
call_api()
238+
call_api()
239+
```
240+
241+
242+
## Validate Your Inputs
243+
244+
Make sure your functions get the right types of data:
245+
246+
```python
247+
def validate_types(**expected_types):
248+
def decorator(func):
249+
@functools.wraps(func)
250+
def wrapper(*args, **kwargs):
251+
# Get function parameter names
252+
import inspect
253+
sig = inspect.signature(func)
254+
bound_args = sig.bind(*args, **kwargs)
255+
bound_args.apply_defaults()
256+
257+
for param_name, expected_type in expected_types.items():
258+
if param_name in bound_args.arguments:
259+
value = bound_args.arguments[param_name]
260+
if not isinstance(value, expected_type):
261+
raise TypeError(
262+
f"{param_name} must be {expected_type.__name__}, "
263+
f"got {type(value).__name__}"
264+
)
265+
266+
return func(*args, **kwargs)
267+
return wrapper
268+
return decorator
269+
270+
@validate_types(name=str, age=int)
271+
def create_user(name, age):
272+
return f"User {name}, age {age}"
273+
274+
# This works
275+
user1 = create_user("Alice", 25)
276+
print(user1) # User Alice, age 25
277+
278+
# This raises TypeError
279+
try:
280+
user2 = create_user("Bob", "twenty-five")
281+
except TypeError as e:
282+
print(e) # age must be int, got str
283+
```
284+
285+
286+
## When to Use Each Decorator
287+
288+
| Decorator Type | Best For | Example Use Cases |
289+
| :--------------- | :----------------------------- | :------------------------------------------------ |
290+
| **Timer** | Performance monitoring | Finding slow functions, optimization |
291+
| **Debug/Logger** | Development \& troubleshooting | Understanding function calls, debugging |
292+
| **Auth** | Security \& access control | Protecting admin functions, user permissions |
293+
| **Cache** | Expensive computations | Database queries, API calls, complex calculations |
294+
| **Retry** | Unreliable operations | Network requests, file operations |
295+
| **Rate Limit** | Controlling frequency | API calls, preventing spam |
296+
| **Validation** | Data integrity | User input, API parameters |
297+
298+
## Tips for Using Decorators
299+
300+
**Always use `@functools.wraps`** – This preserves the original function's name and documentation, making debugging easier.
301+
302+
**Keep them simple** – If your decorator is getting complex, consider if it should be a class or separate function instead.
303+
304+
**Think about order** – When stacking decorators, the one closest to the function runs first:
305+
306+
```python
307+
@timer
308+
@debug
309+
def my_function():
310+
pass
311+
312+
# This is the same as:
313+
# my_function = timer(debug(my_function))
314+
```
315+
316+
**Don't overuse them** – Decorators are powerful, but too many can make code hard to follow.
317+
318+
## Key Takeaways
319+
320+
Decorators let you add functionality to functions without changing their code. They're perfect for cross-cutting concerns like timing, logging, authentication, and caching.
321+
322+
Start with the simple patterns shown here. Once you're comfortable, you can create more sophisticated decorators for your specific needs. The key is understanding that decorators are just functions that wrap other functions – everything else is just clever applications of that basic concept.
323+
324+
Want to practice? Try adding the `@timer` decorator to some of your existing functions and see which ones are slower than you expected. You might be surprised at what you discover!

‎public/blog/python-decorators.jpg

163 KB
Loading[フレーム]

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /