Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
190: Stacking Multiple Decorators
I've noticed a recurring trend when developers first start stacking decorators: they assume that the decorators execute in a simple, linear top-to-bottom order, like a list of instructions. It seems intuitive—you read the code from the top down, so why wouldn't the logic follow suit?
The "Top-to-Bottom" Execution Myth
If you think of decorators as a simple sequence, you might expect that the first decorator listed is the first one to "do its thing" and then simply hand the result to the next one. Let's look at this example to see why that's not exactly what's happening under the hood.
def make_bold(func):
def wrapper():
print("Applying Bold...")
return f"{func()}"
return wrapper
def make_italic(func):
def wrapper():
print("Applying Italic...")
return f"{func()}"
return wrapper
@make_bold
@make_italic
def get_text():
return "Hello World"
print(get_text())
If this were a simple top-down list, you'd expect "Applying Bold" to print first, then "Applying Italic". But when you run this, you'll see "Applying Bold" first, and the resulting string will be <b><i>Hello World</i></b>. Wait, that does look top-down, right? Here is the catch: the wrapping happened from the bottom up, even if the execution of the outer wrapper starts first.
The Onion: Wrapping from the Inside Out
To really get this, you have to stop thinking of decorators as "steps" and start thinking of them as layers of an onion. When Python sees those @ symbols, it's actually performing a series of function assignments. The code above is mathematically equivalent to this:
get_text = make_bold(make_italic(get_text))
Python evaluates the innermost parentheses first. It passes get_text into make_italic. That returns a wrapper. Then, it passes that wrapper into make_bold. By the time you actually call get_text(), you aren't calling your original function at all; you're calling the outermost wrapper (Bold), which then calls the next wrapper (Italic), which finally calls the original function.
This distinction is critical when your decorators have side effects. Imagine you have an @authenticated decorator and a @log_request decorator. If you put @log_request on top, you'll log every single attempt to hit that function, even from unauthenticated users. If you put @authenticated on top, the logger will never even be triggered for guests because the authentication wrapper will throw an error (or return a redirect) before it ever calls the "inner" logger wrapper.
I always tell my juniors: the decorator closest to the function definition is the "tightest" wrap. Everything else is just adding more layers to the outside of the onion.
📋 Practical Task
Building a Rate-Limited API Logger
You are building a sensitive API endpoint. You need to ensure that you aren't logging requests from users who are being rate-limited, as that would bloat your logs with useless "429 Too Many Requests" entries.
Create two decorators:
@rate_limit: This should check a global variablerequest_count. Ifrequest_countis greater than 3, it should print "Rate limit exceeded!" and returnNonewithout calling the wrapped function. Otherwise, it should incrementrequest_countand call the function.@log_call: This should print "Logging request to database..." and then call the wrapped function.
Apply them to a function called fetch_data() that simply returns "Data retrieved". Crucially, stack them so that the rate limiting happens before the logging. Then, call fetch_data() in a loop 5 times and verify that "Logging request to database..." only appears 3 times.
There are no comments for now.