<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Build, Break, Learn]]></title><description><![CDATA[This blog is where I document what I learn, break down what I build, and share the lessons I pick up along the way, so you don't have to figure it all out alone.

I started my career in Diplomacy and International Relations before making a pivot into Software Engineering. That journey taught me that the most valuable skill isn't what you already know, but being willing to start from zero. That philosophy shapes everything I write here.
You'll find articles on web development, AI, EdTech, career transitions, and the realities of building software in the real world. Whether it's a deep dive into a framework, a debugging story, or a reflection on what it means to learn and grow in tech, every post comes from something I've actually worked through myself.

I write for developers, career switchers, self-taught learners, and anyone who believes that technology can make education more accessible, especially across Africa.
If something I write saves you a few hours of frustration or sparks a new idea, then this blog is doing its job.]]></description><link>https://blog.moussaamzat.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6992248b670db28ecd344dea/8797fd77-9318-4a3c-9dc2-7e6d6aaa59a8.png</url><title>Build, Break, Learn</title><link>https://blog.moussaamzat.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 21 Apr 2026 08:24:48 GMT</lastBuildDate><atom:link href="https://blog.moussaamzat.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Generator Functions in Python: How to Pause a Function Mid-Execution]]></title><description><![CDATA[Here's something Python can do that JavaScript can't, at least not natively, without async/await:
def count_up():
    yield 1
    yield 2
    yield 3

Call that function and you don't get 1. You also ]]></description><link>https://blog.moussaamzat.dev/python-generator-functions-yield-lazy-evaluation</link><guid isPermaLink="true">https://blog.moussaamzat.dev/python-generator-functions-yield-lazy-evaluation</guid><category><![CDATA[Python]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[backend]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[generators]]></category><dc:creator><![CDATA[Moussa Kalam AMZAT]]></dc:creator><pubDate>Thu, 02 Apr 2026 09:04:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6992248b670db28ecd344dea/13a3d3ca-c85e-4048-8bd3-b7ab60770709.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Here's something Python can do that JavaScript can't, at least not natively, without async/await:</p>
<pre><code class="language-python">def count_up():
    yield 1
    yield 2
    yield 3
</code></pre>
<p>Call that function and you don't get <code>1</code>. You also don't get <code>[1, 2, 3]</code>. You get <strong>a generator object</strong>; a paused function waiting to be resumed.</p>
<pre><code class="language-python">gen = count_up()
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3
</code></pre>
<p>That's the core idea of generators: functions that can be <strong>paused at a specific point and resumed later</strong>, picking up exactly where they left off (local variables, execution position, everything...)</p>
<p>This article is about understanding generators from the ground up. And there's a practical reason to care beyond generators themselves: <strong>Python's</strong> <code>@contextmanager</code> <strong>decorator, the cleanest way to build context managers, is powered entirely by this mechanism</strong>. Once you understand generators, <code>@contextmanager</code> stops feeling like magic and start feeling obvious. We'll get there in Part 4.</p>
<hr />
<h2>The Problem Generators Solve</h2>
<p>Before we get into the mechanics, let's understand why generators exist.</p>
<p>Imagine you write a function that generates the first <code>n</code> Fibonacci numbers. The naive approach returns a list:</p>
<pre><code class="language-python">def fibonacci(n):
    numbers = []
    a, b = 0, 1
    for _ in range(n):
        numbers.append(a)
        a, b = b, a + b
    return numbers

for num in fibonacci(10):
    print(num)
</code></pre>
<p>This works, but it has a problem: it builds the <strong>entire list in memory</strong> before returning. If <code>n</code> is 10 million, you're holding 10 million numbers in RAM before you've processed a single one.</p>
<p>What if you only need to process them one at a time? What if you only need the first few? You're paying the full memory cost upfront for no reason.</p>
<p>The generator function would look like this:</p>
<pre><code class="language-python">def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

for num in fibonacci(10):
    print(num)
</code></pre>
<p>This produces the same output, but never holds more than two numbers in memory at once. Each time the <code>for</code> loop asks for the next value, the function runs until it hits <code>yield</code>, hands the value over, and <strong>pauses</strong>. On the next iteration, it picks up right after the <code>yield</code>.</p>
<p>This is the key insight: a generator computes values <strong>on demand</strong>, not all at once.</p>
<hr />
<h2>What <code>yield</code> Actually Does</h2>
<p><code>yield</code> is the keyword that turns a regular function into a generator function. But it does more than just return a value: it <strong>suspends the entire execution state</strong> of the function.</p>
<p>Let's trace through a simple example step by step:</p>
<pre><code class="language-python">def simple_gen():
    print("before first yield")
    yield 1
    print("before second yield")
    yield 2
    print("after last yield")
</code></pre>
<pre><code class="language-python">gen = simple_gen()   # function body doesn't run yet — returns a generator object
</code></pre>
<pre><code class="language-python">val = next(gen)
# prints: "before first yield"
# pauses at `yield 1`
# val = 1
</code></pre>
<pre><code class="language-python">val = next(gen)
# resumes after `yield 1`
# prints: "before second yield"
# pauses at `yield 2`
# val = 2
</code></pre>
<pre><code class="language-python">val = next(gen)
# resumes after `yield 2`
# prints: "after last yield"
# function body finishes - raises StopIteration
</code></pre>
<p>Notice: calling <code>simple_gen()</code> doesn't execute a single line of the function body. It just create the generator object. The body only starts running when you call <code>next()</code> for the first time.</p>
<hr />
<h2><code>StopIteration</code> - How Generators Signal "Done"</h2>
<p>When a generator's function body finishes, either by reaching the end of hitting a <code>return</code> statement, it raises <code>StopIteration</code>. This is Python's built-in signal that an iterator has no more values.</p>
<pre><code class="language-python">def two_values():
    yield "first"
    yield "second"
    # function ends here — StopIteration raised automatically

gen = two_values()
print(next(gen))  # "first"
print(next(gen))  # "second"
print(next(gen))  # raises StopIteration 💥
</code></pre>
<p>In practice, you almost never call <code>next()</code> manually and catch <code>StopIteration</code> yourself. The <code>for</code> loop handles it automatically:</p>
<pre><code class="language-python">for value in two_values():
    print(value)
# "first"
# "second"
# loop exits cleanly when StopIteration is raised
</code></pre>
<p>This is why generators work seamlessly in <code>for</code> loops. The loop protocol calls <code>next()</code> under the hood and catches <code>StopIteration</code> to know when to stop.</p>
<hr />
<h2>Generators Are Lazy</h2>
<p>This is the core behavioral difference from a regular function returning a list: generators are <strong>lazy</strong>. They don't compute values until they're asked for.</p>
<pre><code class="language-python">def big_range(n):
    i = 0
    while i &lt; n:
        yield i
        i += 1

# This creates a generator instantly — no computation yet
gen = big_range(1_000_000_000)

# Only NOW does computation happen — and only for one value
print(next(gen))  # 0
print(next(gen)) # 1
</code></pre>
<p>Compare that to <code>list(range(1_000_000_000))</code>, which would try to allocate roughly 8GB of memory immediately.</p>
<p>Laziness makes generators ideal for:</p>
<ul>
<li><p><strong>Large datasets</strong>: Process a file line by line without loading it all into memory</p>
</li>
<li><p><strong>Infinite sequences</strong>: A generator can produce values forever; a list can't.</p>
</li>
<li><p><strong>Pipelines</strong>: Chain generators together so that data flows through transformations one value at a time</p>
</li>
</ul>
<hr />
<h2>Generators Remember Their State</h2>
<p>This is what makes generators genuinely different from callbacks or regular functions. Every local variable, every loop counter, the exact position in the code, all of it is <strong>preserved between calls to</strong> <code>next()</code>.</p>
<pre><code class="language-python">def stateful_counter(start, step):
    current = start
    while True:
        yield current
        current += step   # this runs after each yield, before the next one

counter = stateful_counter(10, 3)
print(next(counter))  # 10
print(next(counter))  # 13
print(next(counter))  # 16
print(next(counter))  # 19
</code></pre>
<p><code>current</code> persists between calls. No class, no instance variables, no global state. The generator object itself carries the state.</p>
<hr />
<h2>Generator Expressions</h2>
<p>Just like list comprehensions give you a concise way to build lists, <strong>generator expressions</strong> give you a concise way to build generators, with the same lazy evaluation.</p>
<p>The syntax is identical to a list comprehension, but with parentheses instead of square brackets:</p>
<pre><code class="language-python"># List comprehension — builds entire list in memory immediately
squares_list = [x ** 2 for x in range(10)]

# Generator expression — lazy, computes one value at a time
squares_gen = (x ** 2 for x in range(10))
</code></pre>
<pre><code class="language-python">print(squares_list)  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
print(squares_gen)   # &lt;generator object &lt;genexpr&gt; at 0x...&gt;
print(next(squares_gen))  # 0
print(next(squares_gen)) # 1
</code></pre>
<p>They work identically in <code>for</code> loops:</p>
<pre><code class="language-python">for sq in (x ** 2 for x in range(5)):
    print(sq)
</code></pre>
<p>And they shine when passed directly to functions that consume iterables:</p>
<pre><code class="language-python"># List comprehension — computes all squares, builds list, then sums
total = sum([x ** 2 for x in range(1_000_000)])

# Generator expression — sums one value at a time, never holds the full list
total = sum(x ** 2 for x in range(1_000_000))
</code></pre>
<p>The second version uses a fraction of the memory. Notice that you can drop the extra parentheses when a generator expression is the only argument to a function call.</p>
<hr />
<h2>When to Use Each</h2>
<table>
<thead>
<tr>
<th>Situation</th>
<th>Use</th>
</tr>
</thead>
<tbody><tr>
<td>Need all values at once, or index into them</td>
<td>List comprehension or <code>list()</code></td>
</tr>
<tr>
<td>Processing values one at a time</td>
<td>Generator expression</td>
</tr>
<tr>
<td>Complex stateful logic between values</td>
<td>Generator function with <code>yield</code></td>
</tr>
<tr>
<td>Potentially infinite sequence</td>
<td>Generator function</td>
</tr>
<tr>
<td>Passing to <code>sum()</code>, <code>max()</code>, <code>min()</code>, <code>any()</code>, <code>all()</code></td>
<td>Generator expression</td>
</tr>
</tbody></table>
<p>A good rule of thumb: If you're building a collection to iterate over it once, a generator is probably the right tool.</p>
<hr />
<h2>Real-World Usage 1: Reading Large Files</h2>
<p>The most practical everyday use of generators is reading files line by line without loading the whole file into memory:</p>
<pre><code class="language-python">def read_large_file(filepath):
    with open(filepath, "r", encoding="utf-8") as f:
        for line in f:
            yield line.strip()

for line in read_large_file("access.log"):
    if "ERROR" in line:
        print(line)
</code></pre>
<p>The file is read one line at a time. If the log file is 10GB, this uses essentially no extra memory, compared to <code>f.readlines()</code> which would load all 10GB into RAM.</p>
<hr />
<h2>Real-World Usage 2: Infinite Sequences</h2>
<p>Generators are the natural tool for sequences that have no defined end:</p>
<pre><code class="language-python">def unique_ids(prefix="user"):
    count = 0
    while True:
        yield f"{prefix}_{count}"
        count += 1

id_gen = unique_ids()
print(next(id_gen))  # user_0
print(next(id_gen))  # user_1
print(next(id_gen))  # user_2
</code></pre>
<p>You'd never build an infinite list. But an infinite generator is perfectly reasonable. You just take as many values as you need.</p>
<hr />
<h2>The JavaScript Parallel</h2>
<p>If you're coming from JavaScript, generators will look familiar. Javascript has them too, with the <code>function*</code> syntax, and the same <code>yield</code> keyword:</p>
<pre><code class="language-javascript">// JavaScript generator
function* fibonacci() {
    let a = 0, b = 1;
    while (true) {
        yield a;
        [a, b] = [b, a + b];
    }
}

const gen = fibonacci();
console.log(gen.next().value);  // 0
console.log(gen.next().value);  // 1
console.log(gen.next().value);  // 1
</code></pre>
<p>Python's generators came first, introduced by <a href="https://peps.python.org/pep-0255/">PEP 255 in Python 2.2</a> and JavaScript borrowed the concept later. The mechanics are nearly identical, with one key difference: in Python, <code>next(gen)</code> is a built-function, while in JavaScript it's <code>gen.next()</code>, a method on the generator object.</p>
<hr />
<h2>What You Can't Do with a Generator</h2>
<p>A few important constraints worth knowing:</p>
<p><strong>You can only iterate once.</strong> A generator is exhausted after you've consumed all its values. You can't reset it or iterate it again, you have to create a new one.</p>
<pre><code class="language-python">gen = (x ** 2 for x in range(3))
print(list(gen))  # [0, 1, 4]
print(list(gen))  # [] — already exhausted
</code></pre>
<p><strong>You can't index into a generator.</strong> Unlike lists, generators don't support <code>gen[2]</code>. They only know "next".</p>
<pre><code class="language-python">gen = (x for x in range(10))
print(gen[3])  # TypeError: 'generator' object is not subscriptable
</code></pre>
<p>If you need random access, convert to a list first: <code>list(gen)[3]</code>.</p>
<hr />
<h2>The Bridge to Context Managers</h2>
<p>Here's why generators matter for this series.</p>
<p>When Python see <code>yield</code> inside a function, it treats the function completely differently. It becomes a generator function.</p>
<p>The <code>@contextmanager</code> decorator from <code>contextlib</code> takes exactly this mechanism and wires to <code>__enter__</code> and <code>__exit__</code>:</p>
<pre><code class="language-python">from contextlib import contextmanager

@contextmanager
def managed_resource():
    print("setup")    # __enter__ — runs up to yield
    yield             # your with block runs here
    print("teardown") # __exit__ — runs after yield
</code></pre>
<p>The <code>yield</code> is literally a pause point. <code>@contextmanager</code> calls <code>next()</code> to run setup, then calls <code>next()</code> again after your <code>with</code> block finishes to run teardown.</p>
<p>You don't need to understand the full wiring yet, that's Article 4. But now you know the mechanism it relies on, and it's not magic. It's just a generator being driven by a decorator.</p>
<hr />
<h2>The Mental Model to Take Away</h2>
<p>A generator function is a <strong>resumable function</strong>. Every time you ask for the next value, it runs until the next <code>yield</code>, hands the value, and freezes, preserving every local variable and its position in the code exactly as they were.</p>
<p>Three things to remember:</p>
<ul>
<li><p><code>yield</code> pauses the function and send a value out</p>
</li>
<li><p><code>next()</code> resumes it from exactly where it paused</p>
</li>
<li><p><code>StopIteration</code> signals the generator is exhausted and <code>for</code> loops handle this automatically</p>
</li>
</ul>
<hr />
<h2>Acronyms Used in This Article</h2>
<ul>
<li><p><strong>PEP -</strong> Python Enhancement Proposal: It's a design document used by the Python community to propose and discuss new language features. <a href="https://peps.python.org/pep-0255/">PEP 255</a> introduced generators and <a href="https://peps.python.org/pep-0289/#abstract">PEP 289</a> introduced generator expressions.</p>
</li>
<li><p><strong>RAM</strong> - Random Access Memory: It's the working memory your computer uses to store data while a program is running.</p>
</li>
</ul>
<hr />
<blockquote>
<p><strong>This is part 3 of 5 in the Python Context Managers Series.</strong></p>
<p>Next up: <strong>Part 4 - Building Context Managers:</strong> <code>__enter__</code>, <code>__exit__</code> and <code>@contextmanager</code></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[The with Statement and open() in Depth]]></title><description><![CDATA[In Part 1, we established the problem: resources need to be returned, code between acquiring and releasing can fail, and try/finally is the manual safety net. Now let's look at the cleaner solution Py]]></description><link>https://blog.moussaamzat.dev/python-with-statement-and-open-in-depth</link><guid isPermaLink="true">https://blog.moussaamzat.dev/python-with-statement-and-open-in-depth</guid><category><![CDATA[Python]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[backend]]></category><category><![CDATA[file I/O]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Moussa Kalam AMZAT]]></dc:creator><pubDate>Mon, 30 Mar 2026 07:53:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6992248b670db28ecd344dea/7f64f030-ca55-436e-900e-918a936053b8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In Part 1, we established the problem: resources need to be returned, code between acquiring and releasing can fail, and <code>try/finally</code> is the manual safety net. Now let's look at the cleaner solution Python provides, and go deep on the most common context manager you'll ever use.</p>
<hr />
<h2>The <code>with</code> Statement: Clean Resource Management</h2>
<p>Here's the <code>try/finally</code> pattern from Part 1:</p>
<pre><code class="language-python">file = open("notes.txt", "w")
try:
    file.write("Hello from Python")
finally:
    file.close()
</code></pre>
<p>And here's the same thing with <code>with</code>:</p>
<pre><code class="language-python">with open("notes.txt", "w") as file:
    file.write("Hello from Python")
# file is automatically closed here — no matter what
</code></pre>
<p>Same guarantee, half the code, and crucially, no way to forget the cleanup. The <code>with</code> statement handles it for you.</p>
<hr />
<h2>Anatomy of the <code>with</code> Statement</h2>
<p>Let's name every piece:</p>
<pre><code class="language-python">with open("notes.txt", "w") as file:
    file.write("Hello from Python")
</code></pre>
<pre><code class="language-plaintext">with  EXPRESSION  as  NAME:
         │              │
         │         the object you use inside the block
         │
something that returns a context manager
</code></pre>
<ul>
<li><p><code>with</code>: the keyword that opens a managed block</p>
</li>
<li><p><code>open("notes.txt", "w")</code>: returns a file object that knows how to manage itself</p>
</li>
<li><p><code>as file</code>: binds the file object to the name <code>file</code> so you can use it</p>
</li>
<li><p><strong>the indented block</strong>: you code runs here</p>
</li>
<li><p><strong>after the block</strong>: cleanup happens automatically, whether you left normally or via an exception</p>
</li>
</ul>
<p>The <code>as</code> clause is optional. Some context managers do their job without needing to reference the object:</p>
<pre><code class="language-python">import threading

lock = threading.Lock()

with lock:  # no `as` needed — you just want acquire/release behavior
    print("only one thread runs this at a time")
</code></pre>
<hr />
<h2>Proving the Guarantee Holds on Error</h2>
<p>Don't take the guarantee on faith. Let's verify it:</p>
<pre><code class="language-python">try:
    with open("notes.txt", "w") as file:
        file.write("first line\n")
        raise ValueError("something exploded")
        file.write("second line\n")  # never runs
except ValueError:
    pass

print(file.closed)  # True — closed despite the error
</code></pre>
<p>Compare that to the broken version without <code>with</code>:</p>
<pre><code class="language-python">try:
    file = open("notes.txt", "w")
    file.write("first line\n")
    raise ValueError("something exploded")
    file.close()  # never runs
except ValueError:
    pass

print(file.closed) # False - still open, leaked
</code></pre>
<p>Run both. The difference is clear. <code>file.closed</code> is your proof.</p>
<hr />
<h2>The JavaScript Parallel</h2>
<p>Coming from Javascript, this pattern should feel familiar. The <code>with</code> statement is conceptually identical to <code>try/finally</code>, which you've probably written in <code>Node.js</code>. There's also the <a href="https://github.com/tc39/proposal-explicit-resource-management">TC39 Explicit Resource Management proposal</a> (already in TypeScript 5.2+) that introduces a <code>using</code> keyword with the same intent:</p>
<pre><code class="language-typescript">// TypeScript 5.2+ — using keyword
await using file = await fs.open("notes.txt", "w");
// file auto-disposes when block exits
</code></pre>
<p>Python has had this built in since <strong>2.5</strong> (2006, via PEP 343). The JavaScript ecosystem is just now catching up, which says something about how useful the pattern is.</p>
<hr />
<h2>Deep Dive: <code>open()</code></h2>
<p><code>open()</code> is the context manager you'll reach for most often. Let's understand it fully, not just the happy path.</p>
<h3>What <code>open()</code> Actually Does</h3>
<p>When you call <code>open()</code>, Python asks the OS to:</p>
<ol>
<li><p>Find the file at the given path</p>
</li>
<li><p>Reserve a file descriptor for your process</p>
</li>
<li><p>Return a <strong>file object</strong> that wraps that descriptor</p>
</li>
</ol>
<p>That file object is what <code>as file</code> binds. It's not the file's contents, it's a handle you use to interact with the file. Think of it like a TV remote: the remote isn't the TV, but it's what lets you control it.</p>
<h3>The Full Signature</h3>
<pre><code class="language-python">open(file, mode='r', encoding=None, buffering=-1, errors=None)
</code></pre>
<p>The two parameters you'll use constantly are <code>file</code> and <code>mode</code>.</p>
<h3>Parameter 1: <code>file</code> - What to Open</h3>
<pre><code class="language-python">open("notes.txt")                       # looks in current working directory
open("data/notes.txt")                  # relative path
open("/home/moussa/projects/notes.txt") # absolute path
</code></pre>
<p>If the file doesn't exist and you're trying to <strong>read</strong> it.</p>
<pre><code class="language-python">with open("ghost.txt", "r") as f:
    content = f.read()
# FileNotFoundError: [Errno 2] No such file or directory: 'ghost.txt'
</code></pre>
<p>If you're <strong>writing</strong>, Python creates the file automatically.</p>
<h3>Parameter 2: <code>mode</code> - What You Intend to Do</h3>
<p>The mode tells Python (and the OS) how you want to interact with the file. It matters as the OS grants different permissions based on intent.</p>
<table>
<thead>
<tr>
<th>Mode</th>
<th>Name</th>
<th>Behavior</th>
</tr>
</thead>
<tbody><tr>
<td><code>"r"</code></td>
<td>read</td>
<td>Default. The file must exist and the cursor is at the file start.</td>
</tr>
<tr>
<td><code>"w"</code></td>
<td>write</td>
<td>It creates the file if it doesn't exist yet and writes to it. If the file exists, it <strong>wipes existing content</strong>.</td>
</tr>
<tr>
<td><code>"a"</code></td>
<td>append</td>
<td>It creates the file if it doesn't exist yet, writes to it and add to its ends. It basically appends and never wipes.</td>
</tr>
<tr>
<td><code>"x"</code></td>
<td>exclusive create</td>
<td>It creates the file and <strong>fails if the file already exists</strong>.</td>
</tr>
<tr>
<td><code>"r+"</code></td>
<td>read + write</td>
<td>The file must exist. It allows you to both read and write.</td>
</tr>
</tbody></table>
<p>You combine modes with a type modifier:</p>
<table>
<thead>
<tr>
<th>Modifier</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td><code>"b"</code></td>
<td>Binary mode: raw bytes, no encoding</td>
</tr>
<tr>
<td><code>"t"</code></td>
<td>Text mode: It's the default and it handles encoding.</td>
</tr>
</tbody></table>
<pre><code class="language-python">open("image.png", "rb")   # read binary — for images, PDFs, etc.
open("data.bin", "wb")    # write binary
open("notes.txt", "rt")   # read text — same as just "r"
</code></pre>
<h3>The Mode That Silently Destroys Your Data</h3>
<p><code>"w"</code> is the mode most beginners get burned by. It doesn't append, it <strong>destroys</strong> the existing content the moment you open the file, before you've written a single byte:</p>
<pre><code class="language-python"># First write
with open("log.txt", "w") as f:
    f.write("Entry 1\n")

# This WIPES "Entry 1" completely
with open("log.txt", "w") as f:
    f.write("Entry 2\n")

# log.txt now contains only "Entry 2" — Entry 1 is gone
</code></pre>
<p>If you want to keep existing content and add to it, use <code>"a"</code>:</p>
<pre><code class="language-python">with open("log.txt", "a") as f:
    f.write("Entry 2\n")  # Entry 1 is still there
</code></pre>
<h3>Parameter 3: <code>encoding</code> - Always Specify It</h3>
<p>This is the one that causes bugs across operating systems. When you don't specify <code>encoding</code>, Python uses the system default, which differs by platform:</p>
<ul>
<li><p><strong>Linux / macOS</strong>: UTF-8 (almost always)</p>
</li>
<li><p><strong>Windows</strong>: A legacy encoding like <code>cp1252</code>, which cannot represent most Unicode characters.</p>
</li>
</ul>
<p>According to <a href="https://peps.python.org/pep-0597/">PEP 597,</a> of the 4,000 most downloaded packages on PyPI, 82 fail to install from source on Windows due to missing encoding specification, and that's just the ones with non-ASCII characters in their README.</p>
<pre><code class="language-python"># Risky — encoding depends on the OS
with open("notes.txt", "r") as f:
    content = f.read()

# Explicit — works the same everywhere
with open("notes.txt", "r", encoding="utf-8") as f:
    content = f.read()
</code></pre>
<p>One character can prevent a whole class of cross-platform bugs. Always specify <code>encoding="utf-8"</code>.</p>
<blockquote>
<p>Note: Python 3.15 will make UTF-8 the default via <a href="https://peps.python.org/pep-0686/">PEP 686.</a> Until then, specify it explicitly.</p>
</blockquote>
<hr />
<h2>Reading and Writing: The Core Methods</h2>
<p>Once you have a file object, these are the methods you'll use day to day.</p>
<h3>Reading</h3>
<pre><code class="language-python"># Read the entire file as one string
with open("notes.txt", "r", encoding="utf-8") as f:
    content = f.read()
    print(content)

# Read one line at a time
with open("notes.txt", "r", encoding="utf-8") as f:
    first_line = f.readline()
    print(first_line)
    second_line = f.readline()
    print(second_line)
    

# Read all lines into a list
with open("notes.txt", "r", encoding="utf-8") as f:
    lines = f.readlines()
    print(lines)  # ["line 1\n", "line 2\n", ...]
</code></pre>
<p>The most Pythonic way to read line by line is to iterate directly over the file object. It's memory-efficient as it doesn't load the entire file at once, which matters when you're processing large files:</p>
<pre><code class="language-python">with open("notes.txt", "r", encoding="utf-8") as f:
    for line in f:            # file objects are iterable
        print(line.strip())   # strip() removes the trailing \n
</code></pre>
<h3>Writing</h3>
<pre><code class="language-python"># Write a string — \n is your responsibility
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("Hello, Moussa\n")
    f.write("Second line\n")

# Write a list of strings at once
with open("notes.txt", "w", encoding="utf-8") as f:
    lines = ["line 1\n", "line 2\n", "line 3\n"]
    f.writelines(lines)
</code></pre>
<p>Note: <code>write()</code> does <strong>not</strong> add a newline automatically. You have to include <code>\n</code> yourself. This catches people coming from <code>print()</code>, which adds it for you.</p>
<hr />
<h2>The File Cursor</h2>
<p>Every file object tracks your current position using an internal <strong>cursor.</strong> This trips people up when they try to read a file they just wrote:</p>
<pre><code class="language-python">with open("notes.txt", "r", encoding="utf-8") as f:
    first_five = f.read(5)   # reads first 5 characters
    print(first_five)        # "Hello"

    rest = f.read()          # cursor is now at position 5
    print(rest)              # reads everything after position 5
</code></pre>
<p>You can move the cursor manually with <code>seek()</code>:</p>
<pre><code class="language-python">with open("notes.txt", "r", encoding="utf-8") as f:
    f.read()         # reads everything — cursor now at end
    print(f.read())  # empty string — nothing left

    f.seek(0)        # reset cursor to the beginning
    print(f.read())  # reads everything again
</code></pre>
<hr />
<h2>Why <code>open()</code> Is a Context Manager</h2>
<p>Here is the connection back to where we started. File objects implement two special methods: <code>__enter__</code> and <code>__exit__</code> which is what makes them work with <code>with</code>.</p>
<p>Simplified, this is what they do:</p>
<pre><code class="language-python">class FileObject:
    def __enter__(self):
        return self     # return the file object itself

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()    # always close the file
        return False    # never suppress exceptions
</code></pre>
<p>That's it. When Python sees <code>with open(...) as f</code>, it calls <code>__enter__</code> to get the file object and <code>__exit__</code> to close it. Everything else follows from that.</p>
<p>We'll look at how <code>__enter__</code> and <code>__exit__</code> actually work, and how to implement them yourself in Part 3.</p>
<hr />
<h2>Quick Reference</h2>
<pre><code class="language-python"># Read entire file
with open("data.txt", "r", encoding="utf-8") as f:
    content = f.read()

# Write (creates or overwrites)
with open("data.txt", "w", encoding="utf-8") as f:
    f.write("some content\n")

# Append (never overwrites)
with open("data.txt", "a", encoding="utf-8") as f:
    f.write("new line\n")

# Read line by line (memory efficient)
with open("data.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.strip())

# Read binary (images, PDFs, etc.)
with open("image.png", "rb") as f:
    data = f.read()
</code></pre>
<h2>The Mental Model to Take Away</h2>
<p>The <code>with</code> statement activates a contract: <strong>setup on entry, guaranteed teardown on exit</strong>. <code>open()</code> is the most common thing that honors that contract. It opens a file descriptor on entry and closes it on exit, no matter what happens in between.</p>
<p>Two rules to carry forward:</p>
<ol>
<li><p>Always use <code>with</code> when opening files. Never rely on <code>.close()</code> or the garbage collector.</p>
</li>
<li><p>Always specify <code>encoding=utf-8</code>. Never rely on the OS default.</p>
</li>
</ol>
<p>In part 3, we'll pull back the curtain on how the <code>with</code> statement works under the hood and build your own context manager from scratch.</p>
<p>Thank you for reading! 🙂</p>
<hr />
<blockquote>
<p><strong>This is Part 2 of 5 in the Python Context Managers series.</strong></p>
<p><strong>New here?</strong> <a href="https://blog.moussaamzat.dev/why-python-makes-you-close-things"><strong>Read Part 1</strong></a></p>
<p>Next up: <strong>Part 3 - Generator Functions in Python</strong></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Why Python Makes you "Close" Things (And What Happens When You Don't)]]></title><description><![CDATA[Coming from JavaScript/TypeScript, I thought I understood resource management. You fetch(), you await, you move on. Node.js handles the rest, right?
Then I started writing Python seriously, and I kept]]></description><link>https://blog.moussaamzat.dev/why-python-makes-you-close-things</link><guid isPermaLink="true">https://blog.moussaamzat.dev/why-python-makes-you-close-things</guid><category><![CDATA[Python]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[backend]]></category><category><![CDATA[software development]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Moussa Kalam AMZAT]]></dc:creator><pubDate>Fri, 27 Mar 2026 06:58:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6992248b670db28ecd344dea/f354058a-dd13-4b74-96eb-c3660dad238c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Coming from JavaScript/TypeScript, I thought I understood resource management. You <code>fetch()</code>, you <code>await</code>, you move on. Node.js handles the rest, right?</p>
<p>Then I started writing Python seriously, and I kept on seeing this part everywhere:</p>
<pre><code class="language-python">with open("data.txt", "r") as file:
    content = file.read()
</code></pre>
<p>I knew <strong>how</strong> to use it. Always writing the same bit of syntax, it works. But I had no idea <strong>why</strong> it existed, or what happened if I skipped it.<br />It turns out a lot can go wrong. And the worst part is that it won't blow up immediately. It will fail silently, in production, at 2am on a Saturday.</p>
<p>This article is about understanding the problem that Python's <code>with</code> statement was built to solve. We'll get to <code>with</code> in the next article. First, let's talk about what happens when things go wrong.</p>
<hr />
<h2>Your Program Borrows Things from the OS</h2>
<p>When your Python program wants to read a file, it can't just reach into the filesystem directly. It has to ask the operating system nicely.<br />The OS responds by opening the file and handing your program a <strong>file descriptor</strong>, a small non-negative integer that acts as a reservation ticket. Think of it like the numbered ticket you get at a deli counter. The ticket isn't the sandwich. It's just proof that you're in the queue and the deli is holding your order.</p>
<pre><code class="language-python">file = open("notes.txt", "w")
print(file.fileno()) # prints something like 3, 4, or 5
</code></pre>
<p>Numbers 0, 1, and 2 are permanently reserved by the OS for every process:</p>
<table>
<thead>
<tr>
<th>Descriptor</th>
<th>Name</th>
<th>What it is</th>
</tr>
</thead>
<tbody><tr>
<td><code>0</code></td>
<td><code>stdin</code></td>
<td>Keyboard input</td>
</tr>
<tr>
<td><code>1</code></td>
<td><code>stdout</code></td>
<td>Normal print output</td>
</tr>
<tr>
<td><code>2</code></td>
<td><code>stderr</code></td>
<td>Error output</td>
</tr>
</tbody></table>
<p>So the first file you open typically gets descriptor <code>3</code>, the next gets <code>4</code>, and so on.</p>
<p>Here is the critical part: <strong>the OS keeps a table of open file descriptors per process, and that table has a fixed size.</strong> On most Linux systems, the default limit is 1024.</p>
<pre><code class="language-python">import resource

soft_limit, _ = resource.getrlimit(resource.RLIMIT_NOFILE)
print(f"Max open files: {soft_limit}") # typically 1024
</code></pre>
<p>Once all 1024 slots are taken, any new <code>open()</code> call raises:</p>
<pre><code class="language-plaintext">OSError: [Errno 24] Too many open files
</code></pre>
<p>That ticket counter fills up, and the deli stops taking orders.</p>
<hr />
<h2>The Leak You Don't See Coming</h2>
<p>Here's where it gets sneaky. A resource leak doesn't announce itself. It builds up quietly until the system hits its limit, then it explodes.<br />Consider this function that processes a batch of files:</p>
<pre><code class="language-python">def process_files(filenames):
    for name in filenames:
        file = open(name, "r")
        data = file.read()
        process(data)
        # oops — forgot file.close()
</code></pre>
<p>This works perfectly for the first 1,021 files. Then on file 1022, crash 💥. And the error message points at the <code>open()</code> call, not at the missing <code>close()</code> three lines earlier. Good luck debugging that at 2am!</p>
<p>Let's make it concrete. Here's a script that deliberately leaks file descriptors:</p>
<pre><code class="language-python">import os

files = []
count = 0

try:
    while True:
        f = open("test.txt", "w")
        files.append(f)  # keep a reference so it's not garbage collected
        count += 1
except OSError as e:
    print(f"Failed after {count} open files: {e}")
</code></pre>
<div>
<div>⚠</div>
<div>Please don't run this in production.</div>
</div>

<hr />
<h2>It's Not Just Files</h2>
<p>File descriptors are the most visible example, but the same problem applies to any resource with a limited supply:</p>
<p><strong>Network connections</strong>: databases, APIs, and servers all have connection limits. Leave connections open and you'll eventually see <code>Too many connections</code> errors from your database, with perfectly healthy-looking application code.</p>
<p><strong>Locks</strong>: This one is nastier that a file leak. If your code requires a threading lock and then throws an exception before releasing it, the lock stays held forever. Every other thread waiting on it will wait forever too. Your application freezes. No error message, just silence.</p>
<pre><code class="language-python">import threading

lock = threading.Lock()

def transfer_funds(amount):
    lock.acquire()

    if amount &gt; get_balance():
        raise ValueError("Insufficient funds")  # 💥 lock never released

    deduct(amount)
    lock.release()  # never reached if the check above raises
</code></pre>
<p>Call <code>transfer_funds()</code> with an invalid amount once, and your entire application deadlocks. Every subsequent call to any function that tries to acquire that lock will hang. Congratulations, you've built a very expensive paperweight 😄.</p>
<hr />
<h2>The Data Corruption You Don't Expect</h2>
<p>There's another flavor of resource leak that's even more surprising: <strong>unflushed writes</strong>.</p>
<p>When you write to a file, Python doesn't immediately send those bytes to disk. It buffers them in memory first for performance. The buffer gets flushed to disk with it fills up, when you call <code>.flush()</code>, or crucially, when you call <code>.close()</code>.<br />If your program crashes before <code>.close()</code> is called, the buffer is discarded. The data never makes it to disk.</p>
<pre><code class="language-python">file = open("important_data.txt", "w")
file.write("Transfer $10,000 to account 9982")
raise Exception("something went wrong")
file.close()  # never reached
</code></pre>
<p>Open <code>important_data.txt</code> after running this. Empty!!!<br />Those bytes were sitting in a memory buffer, and when the process died, they went with it.</p>
<p>Let's make it even more dramatic:</p>
<pre><code class="language-python">import os

file = open("test.txt", "w")
file.write("This will never make it to disk")

os._exit(1)  # hard kill — skips all cleanup
</code></pre>
<p>Check <code>test.txt</code> after running that. Empty file. Zero bytes written.</p>
<hr />
<h2>Won't the Garbage Collector Handle It?</h2>
<p>Coming from JavaScript, this was my first instinct too. GC handles memory, but does it handle files too?</p>
<p>Sometimes. CPython (the standard Python implementation) uses <strong>reference counting</strong>: when an object's reference count drops to zero, it gets cleaned up immediately, which includes closing the file:</p>
<pre><code class="language-python">def read_data():
    file = open("notes.txt", "r")
    return file.read()
    # file goes out of scope here
    # CPython closes it immediately — but only because of reference counting
</code></pre>
<p>But here's the problem: <strong>this is an implementation detail of</strong> <code>CPython</code><strong>, not a guarantee of the Python language.</strong> <code>PyPy</code>, <code>Jython</code>, and other Python implementations use different garbage collectors that don't clean up immediately. Even in <code>CPython</code>, circular references can delay cleanup indefinitely.</p>
<p>The <a href="https://arc.net/l/quote/gppxjzsk">Python documentation</a> is explicit about this:</p>
<blockquote>
<p><strong>"It is good practice to use the</strong> <code>with</code> keyword when dealing with file objects. The advantage is that the file is properly closed after its suite finishes, even if an exception is raised at some point.<strong>"</strong></p>
</blockquote>
<p>Don't rely on GC to close your files. It's like assuming someone else will wash your dishes. Maybe they will. Maybe they won't. You'll find out at the worst possible time.</p>
<hr />
<h2>The Manual Fix, And Why It's Not Enough</h2>
<p>Once you understand the problem, the fix seems obvious: use <code>try/finally</code>.</p>
<p><code>finally</code> blocks always run, whether the code inside succeeded, raised an exception, or even hit a <code>return</code> statement.</p>
<pre><code class="language-python">file1 = open("input.txt", "r")
try:
    file2 = open("output.txt", "w")
    try:
        data = file1.read()
        file2.write(data.upper())
    finally:
        file2.close()
finally:
    file1.close()
</code></pre>
<p>This is correct but exhausting to write and read. There has to be a better way. Don't you think so? 🤔</p>
<hr />
<h2>There Is a Better Way</h2>
<p><a href="https://docs.python.org/3/whatsnew/2.5.html">Python 2.5</a> introduced the <code>with</code> statement (via PEP 343) specifically to solve this problem. It packages the <code>try/finally</code> pattern into a clean, reusable interface that's impossible to forget.</p>
<p>The same two-file example from above becomes:</p>
<pre><code class="language-python">with open("input.txt", "r") as file1, open("output.txt", "w") as file2:
    data = file1.read()
    file2.write(data.upper())
# both files are closed here — automatically, always
</code></pre>
<p>No nesting, no <code>finally</code>. No way to forget to cleanup. The same guarantee, with none of the ceremony.</p>
<p>That's what context managers are, a Python-native way to say: <strong>"when this block exits, run this cleanup, no matter what."</strong></p>
<hr />
<h2>The Mental Model to Take Away</h2>
<p>Every resource your program borrows from the OS need to be returned:</p>
<ul>
<li><p>Open a file, close it</p>
</li>
<li><p>Acquire a lock, release it</p>
</li>
<li><p>Open a connection, close it.</p>
</li>
</ul>
<p>The problem isn't that developers don't know this. It's that <strong>code between acquiring and releasing can fail</strong>, and when it does, the cleanup gets skipped.</p>
<p><code>try/finally</code> is the manual solution. Context manager is the automatic one.</p>
<p>In the next article, we'll look at exactly how the <code>with</code> statement works, go deep on <code>open()</code>, and start seeing how Python's most common operation, reading and writing files, becomes foolproof with context managers.</p>
<hr />
<h2>Acronyms Used in This Article</h2>
<ul>
<li><p><strong>OS</strong> — Operating System. The software that manages hardware resources and provides services to programs. On most servers, this is Linux.</p>
</li>
<li><p><strong>GC</strong> — Garbage Collector. The part of a language runtime responsible for automatically reclaiming memory that's no longer in use.</p>
</li>
<li><p><strong>PEP</strong> — Python Enhancement Proposal. A design document used by the Python community to propose and discuss new language features. PEP 343 is the one that introduced the <code>with</code> statement.</p>
</li>
</ul>
<p>Thank you for reading 🙂.</p>
<hr />
<blockquote>
<p><strong>This is Part 1 of 5 in the Python Context Managers Series.</strong></p>
<p><strong>Next up: Part 2 - The</strong> <code>with</code> Statement and <code>open()</code> in Depth.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Class-Based Python Decorators: When Functions Aren't Enough]]></title><description><![CDATA[You've built decorators with functions. You've nested them two layers deep, three layers deep, stacked them on top of each other. And it all works.
But at some point, you'll write a decorator that nee]]></description><link>https://blog.moussaamzat.dev/class-based-python-decorators-when-functions-aren-t-enough</link><guid isPermaLink="true">https://blog.moussaamzat.dev/class-based-python-decorators-when-functions-aren-t-enough</guid><category><![CDATA[Python]]></category><category><![CDATA[decorators]]></category><category><![CDATA[Advanced Python]]></category><category><![CDATA[oop]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Moussa Kalam AMZAT]]></dc:creator><pubDate>Sun, 22 Mar 2026 21:31:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6992248b670db28ecd344dea/54ed0adc-a390-4905-848a-0279406982bf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You've built decorators with functions. You've nested them two layers deep, three layers deep, stacked them on top of each other. And it all works.</p>
<p>But at some point, you'll write a decorator that needs to remember things between calls: a counter, a cache, a log of every invocation... And you'll find yourself reaching <code>nonlocal</code>, juggling closure variables, and wondering if there'a a cleaner way.</p>
<p>There is. It's a class.</p>
<hr />
<h2>A Quick Prerequisite: <code>__call__</code></h2>
<p>Before we build anything, you need to know one thing about Python classes: any object can behave like a function if its class defines a <code>__call__</code> method.</p>
<pre><code class="language-python">class Greeter:
    def __call__(self, name):
        print(f"Hello, {name}!")

greet = Greeter()
greet("Moussa")  # Hello, Moussa!
</code></pre>
<p><code>greet</code> is not a function, it's an instance of <code>Greeter</code>. But because <code>Greeter</code> has <code>__call__</code>, you can use parentheses on it as if it were a function. Python sees <code>greet("Moussa")</code> and internally calls <code>greet.__call__("Moussa")</code>.</p>
<p>This is the entire foundation of class-based decorators. If an object is callable, it can replace a function. And if it can replace a function, it can be a wrapper.</p>
<hr />
<h2>Your First Class-Based Decorator</h2>
<p>Let's start with something familiar, a decorator that logs every time a function is called:</p>
<p><strong>Function-based version (</strong><a href="https://blog.moussaamzat.dev/series/python-decorators"><strong>what you already know</strong></a><strong>):</strong></p>
<pre><code class="language-python">def log_calls(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper
</code></pre>
<p><strong>Class-based version:</strong></p>
<pre><code class="language-python">class LogCalls:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f"Calling {self.func.__name__}")
        return self.func(*args, **kwargs)

@LogCalls
def greet(name):
    return f"Hello, {name}"

print(greet("Moussa"))
# Calling greet
# Hello, Moussa
</code></pre>
<p>Let's trace through what Python does when it sees <code>@LogCalls</code>:</p>
<ol>
<li><p>Python calls <code>@LogsCall(greet)</code>: This triggers <code>__init__,</code> which stores the original function as <code>self.func</code></p>
</li>
<li><p><code>greet</code> is now replaced by the <code>LogCalls</code> instance</p>
</li>
<li><p>When you call <code>greet("Moussa")</code>, Python calls the instance, which triggers <code>__call__</code></p>
</li>
<li><p>Inside <code>__call__</code>, we run our logic and call <code>self.func("Moussa")</code></p>
</li>
</ol>
<p>Same pattern as before: take a function in, return something callable that wraps. The difference is that the "something callable" is now an object instead of an inner function.</p>
<hr />
<h2>Why Bother? The State Problem</h2>
<p>So far, the class version looks like more code for the same result. Fair enough 😀!<br />The real advantage shows up when your decorator needs to maintain state.</p>
<p>Let's build a decorator that counts how many times a function has been called.</p>
<p><strong>Function-based version:</strong></p>
<pre><code class="language-python">def count_calls(func):
    count = 0
    def wrapper(*args, **kwargs):
        nonlocal count
        count += 1
        print(f"{func.__name__} has been called {count} time(s)")
        return func(*args, **kwargs)
    return wrapper
</code></pre>
<p>It works, but there's a problem: <code>count</code> is trapped inside the closure. You can't access it from outside. You can't reset it. You can't inspect it. It's invisible.</p>
<p><strong>Class-based version</strong></p>
<pre><code class="language-python">class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} has been called {self.count} time(s)")
        return self.func(*args, **kwargs)

@CountCalls
def greet(name):
    return f"Hello, {name}"

greet("Moussa")   # greet has been called 1 time(s)
greet("Fabien")   # greet has been called 2 time(s)

print(greet.count)  # 2 — accessible!
greet.count = 0     # reset it
greet("Moussa")     # greet has been called 1 time(s)
</code></pre>
<p>The state lives on <code>self</code>, not inside a closure. You can read it, reset it, and even add methods to interact with it. The decorator becomes a proper object, not a black box.</p>
<p>This is the key insight: <strong>function-based decorators use closure to remember state. Class-based decorators use</strong> <code>self</code>. Same concept, different container, but <code>self</code> is far more accessible.</p>
<hr />
<h2>Adding Methods to Your Decorators</h2>
<p>Since the wrapper is now an object, you can give it useful methods:</p>
<pre><code class="language-python">class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)

    def reset(self):
        self.count = 0

    def report(self):
        print(f"{self.func.__name__} has been called {self.count} time(s)")

@CountCalls
def process_order(order_id):
    return f"Order {order_id} processed"

process_order(1)
process_order(2)
process_order(3)
process_order.report()  # process_order has been called 3 times
process_order.reset()
process_order.report()  # process_order has been called 0 times
</code></pre>
<p>Try doing that with a function-based decorator. You'd have to attach functions as attributes on the wrapper, awkward and messy. With a class, it's natural.</p>
<hr />
<h2>Class-Based Decorators with Arguments</h2>
<p>Just like function-based decorators, you can make class-based decorators configurable. The pattern shifts slightly.</p>
<p><strong>Without arguments</strong>, <code>__init__</code> receives the arguments, and <code>__call__</code> receives the function:</p>
<pre><code class="language-python">@MyDecorator(arg)   # Python calls MyDecorator(arg), then the result(func)
def my_function():
    pass
</code></pre>
<p>Here's a practical example: a decorator that slows down a function by a configurable number of seconds:</p>
<pre><code class="language-python">import time

class SlowDown:
    def __init__(self, seconds):
        self.seconds = seconds

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print(f"Waiting {self.seconds}s before calling {func.__name__}...")
            time.sleep(self.seconds)
            return func(*args, **kwargs)
        return wrapper

@SlowDown(2)
def send_email(to):
    print(f"Email sent to {to}")

send_email("moussa@example.com")
# Waiting 2s before calling send_email...
# Email sent to moussa@example.com
</code></pre>
<p>Let's trace through it:</p>
<ol>
<li><p>Python evaluates <code>SlowDown(2)</code>: This creates an instance with <code>self.seconds = 2</code></p>
</li>
<li><p>Python calls that instance with the function: <code>instance(send_email)</code>, which triggers <code>__call__</code></p>
</li>
<li><p><code>__call__</code> returns the <code>wrapper</code> function</p>
</li>
<li><p><code>send_email</code> is now replaced by <code>wrapper</code></p>
</li>
</ol>
<p>Notice the difference: when there are <strong>no arguments</strong>, <code>__call__</code> is the <strong>wrapper itself</strong> (it gets called every time the decorated function is called). When there <strong>are arguments</strong>, <code>__call__</code> is the <strong>decorator</strong> (it gets called once, receiving the function, and returns a wrapper).</p>
<p>This is the trickiest part of class-based decorators. Read that paragraph again if you need to.</p>
<hr />
<h2>Practical Example: A Rate Limiter</h2>
<p>Here's a class-based decorator that limits how often a function can be called:</p>
<pre><code class="language-python">import time

class RateLimit:
    def __init__(self, min_interval):
        self.min_interval = min_interval

    def __call__(self, func):
        last_called = [0]  # using list to allow mutation in closure

        def wrapper(*args, **kwargs):
            elapsed = time.time() - last_called[0]
            if elapsed &lt; self.min_interval:
                wait = self.min_interval - elapsed
                print(f"Rate limited. Wait {wait:.1f}s")
                return None
            last_called[0] = time.time()
            return func(*args, **kwargs)
        return wrapper

@RateLimit(min_interval=2)
def call_api(endpoint):
    print(f"Calling {endpoint}")
    return {"status": "ok"}

call_api("/users")      # Calling /users
call_api("/users")      # Rate limited. Wait 1.8s
time.sleep(2)
call_api("/users")      # Calling /users
</code></pre>
<p>The <code>min_interval</code> configuration lives on the instance. The call timing state lives in the closure. This hybrid approach: class for configuration, closure for per-call state, is a common and effective pattern.</p>
<hr />
<h2>Practice Example: A Retry with Exponential Backoff</h2>
<pre><code class="language-python">import time

class Retry:
    def __init__(self, max_attempts=3, backoff_factor=2):
        self.max_attempts = max_attempts
        self.backoff_factor = backoff_factor

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            for attempt in range(1, self.max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == self.max_attempts:
                        print(f"All {self.max_attempts} attempts failed.")
                        raise
                    wait = self.backoff_factor ** attempt
                    print(f"Attempt {attempt} failed: {e}. Retrying in {wait}s...")
                    time.sleep(wait)
        return wrapper

@Retry(max_attempts=3, backoff_factor=2)
def fetch_data(url):
    raise ConnectionError("Server unreachable")

# fetch_data("https://api.example.com")
# Attempt 1 failed: Server unreachable. Retrying in 2s...
# Attempt 2 failed: Server unreachable. Retrying in 4s...
# All 3 attempts failed.
</code></pre>
<p>The class makes the configuration readable: <code>max_attempts=3</code>, <code>backoff_factor=2</code> reads like a sentence. Compare this to the function-based version where these would be arguments to a factory function. Same result, but the class version communicates intent more clearly when the configuration is complex.</p>
<hr />
<h2>When to Use Function-Based vs Class-Based</h2>
<p>This isn't a "one is better" situation. They're tools for different contexts:</p>
<p><strong>Use function-based decorators when:</strong></p>
<ul>
<li><p>Your decorator is simple: log, time, validate, then call the function</p>
</li>
<li><p>You don't need to maintain state between calls</p>
</li>
<li><p>You don't need to expose any interface to the caller</p>
</li>
<li><p>You want less boilerplate for a quick wrapper</p>
</li>
</ul>
<p><strong>Use class-based decorators when:</strong></p>
<ul>
<li><p>You need to maintain state across calls (counters, caches, history)</p>
</li>
<li><p>You want to expose methods or properties on the decorated function (<code>.reset()</code>, <code>.count</code>, <code>.report()</code>)</p>
</li>
<li><p>Your decorator has complex configuration with multiple parameters</p>
</li>
<li><p>You want to use inheritance to create decorator families</p>
</li>
<li><p>Readability matters more than brevity, classes make the structure explicit</p>
</li>
</ul>
<p>Most decorators you'll write and encounter are function-based. But when the logic gets complex or stateful, classes keep things organized where closures start to get tangled.</p>
<hr />
<h2>A Note on <code>functools.wraps</code></h2>
<p>One thing we haven't addressed: when you decorate a function with a class, the function's <code>__name__</code> and <code>__doc__</code> get replaced by the class's.</p>
<pre><code class="language-python">@CountCalls
def greet(name):
    """Greet someone by name."""
    return f"Hello, {name}"

print(greet.__name__)  # 'CountCalls' — not 'greet'!
print(greet.__doc__)   # None — the docstring is gone!
</code></pre>
<p>For function-based decorators, you'd use <code>@functools.wraps(func)</code> on the wrapper.<br />For class-based decorators, you can apply it in <code>__init__</code>:</p>
<pre><code class="language-python">import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)

@CountCalls
def greet(name):
    """Greet someone by name."""
    return f"Hello, {name}"

print(greet.__name__)  # 'greet' — preserved!
print(greet.__doc__)   # 'Greet someone by name.' — preserved!
</code></pre>
<p><code>functools.update_wrapper</code> copies the original function's metadata on to the instance. Always do this as it keeps debugging, documentation, and introspection tools working correctly.</p>
<hr />
<h2>The Mental Model</h2>
<p>Here's how function-based and class-based decorators map to each other:</p>
<p><strong>Function-based:</strong></p>
<ul>
<li><p>Outer function receives the function (or arguments)</p>
</li>
<li><p>Inner function (wrapper) replaces the function</p>
</li>
<li><p>Closure variables are the state</p>
</li>
</ul>
<p><strong>Class-based</strong></p>
<ul>
<li><p><code>__init__</code> receives the function (or arguments)</p>
</li>
<li><p><code>__call__</code> replaces the function (or returns the wrapper)</p>
</li>
<li><p><code>self</code> represents the state.</p>
</li>
</ul>
<p>Same architecture, different syntax. If you understand one, you understand both. The only question is which one keeps your code for the specific problem you're solving.</p>
<hr />
<h2>What's Next?</h2>
<p>This article extends the Python Decorators series with a pattern you'll encounter in more advanced codebases, especially in frameworks, ORMs, and testing libraries. If you haven't read the earlier parts, here's the full series:</p>
<ul>
<li><p><a href="https://blog.moussaamzat.dev/understanding-python-decorators-from-scratch">Part 1: Understanding Python Decorators From Scratch</a>: The foundations</p>
</li>
<li><p><a href="https://blog.moussaamzat.dev/print-vs-return-trap-python-decorators">Part 2: The Print vs Return Trap</a>: The most common silent bug</p>
</li>
<li><p><a href="https://blog.moussaamzat.dev/python-decorators-arguments-and-stacking">Part 3: Advanced Patterns — Arguments and Stacking</a>: The real-world patterns</p>
</li>
<li><p><a href="https://blog.moussaamzat.dev/10-exercises-to-master-python-decorators">Part 4: 10 Exercises to Master Decorators</a>: Hands-on practice</p>
</li>
</ul>
<p>Thank you for reading 🙂.</p>
<hr />
<blockquote>
<p>This is a bonus article in the Python Decorators series on Build, Break, Learn.<br />Written by a developer who learned the hard way so you don't have to.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[10 Exercises to Master Python Decorators (With Solutions)]]></title><description><![CDATA[You've read the theory. You understood how decorators work. Now it's time to prove to yourself.
These exercises are ordered deliberately. The first few build the foundation skills, and each one adds a]]></description><link>https://blog.moussaamzat.dev/10-exercises-to-master-python-decorators</link><guid isPermaLink="true">https://blog.moussaamzat.dev/10-exercises-to-master-python-decorators</guid><category><![CDATA[Python]]></category><category><![CDATA[decorators]]></category><category><![CDATA[coding challenge]]></category><category><![CDATA[learn python]]></category><category><![CDATA[practice]]></category><dc:creator><![CDATA[Moussa Kalam AMZAT]]></dc:creator><pubDate>Tue, 17 Mar 2026 08:35:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6992248b670db28ecd344dea/9219368e-88e7-4d33-b745-32e3ba795d8d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You've read the theory. You understood how decorators work. Now it's time to prove to yourself.</p>
<p>These exercises are ordered deliberately. The first few build the foundation skills, and each one adds a layer until you're writing production-style decorators by the end. Try each exercise before looking at the solution. Struggling is where the learning happens.</p>
<p>Let's go.</p>
<hr />
<h2>Exercise 1: Functions as Objects</h2>
<p><strong>Task</strong>: Write a function called <code>apply</code> that takes a function and a value, and returns the result of calling that function with that value.</p>
<pre><code class="language-python">def square(x):
    return x * x

def double(x):
    return x * 2

# Your code should make this work:
print(apply(square, 5))   # 25
print(apply(double, 5))   # 10
</code></pre>
<div>
<div>💡</div>
<div><strong>What this tests</strong>: Can you pass a function as an argument and call it?</div>
</div>

<details>
<summary>Click to reveal solution</summary>
<pre class="not-prose"><code class="language-python">def apply(func, value):
    return func(value)</code></pre><p>Simple. <code>func</code> is just a variable that happens to hold a function. Call it with <code>()</code> and pass the value.</p>
</details>

<hr />
<h2>Exercise 2: Returning a Function from a Function</h2>
<p><strong>Task</strong>: Write a function called <code>multiplier</code> that takes a number <code>n</code> and returns a <em>new</em> function that multiplies any number by <code>n</code>.</p>
<pre><code class="language-python"># Your code should make this work:
times_three = multiplier(3)
times_ten = multiplier(10)
print(times_three(5))   # 15
print(times_ten(5))     # 50
print(times_three(7))   # 21
</code></pre>
<div>
<div>💡</div>
<div><strong>What this tests</strong>: Can you create a closure, a function that remembers a value from its enclosing scope?</div>
</div>

<p><strong>Common mistake:</strong> Writing two separate functions <code>times_three</code> and <code>times_ten</code> by hand. The point is that <code>multiplier</code> creates them dynamically.</p>
<details>
<summary>Click to reveal solution</summary>
<pre class="not-prose"><code class="language-python"> def multiplier(n):
    def inner(x):
        return x * n
    return inner</code></pre><p><code>inner</code> remembers <code>n</code> from when it was created. This is a closure, the exact mechanism that powers decorators.</p>
</details>

<hr />
<h2>Exercise 3: Your First Decorator</h2>
<p><strong>Task</strong>: Write a decorator called <code>shout</code> that converts the return value of a function to uppercase.</p>
<pre><code class="language-python">@shout
def greet(name):
    return f"hello, {name}"

@shout
def farewell(name):
    return f"goodbye, {name}"

print(greet("Moussa"))     # HELLO, MOUSSA
print(farewell("Moussa"))  # GOODBYE, MOUSSA
</code></pre>
<p><strong>What this tests</strong>: Can you write a basic decorator that transforms a return value?</p>
<details>
<summary>Click to reveal solution</summary>
<pre class="not-prose"><code class="language-python">from functools import wraps
</code></pre><p><code>def shout(func):<br />wraps(func)<br />def wrapper(*args, **kwargs):<br />result = func(*args, **kwargs)<br />return result.upper()<br />return wrapper</code></p><p>Notice the pattern: call the function, catch the result, transform it, return the transformed version.</p><p></p>
</details>

<hr />
<h2>Exercise 4: A Counting Decorator</h2>
<p><strong>Task</strong>: Write a decorator called <code>count_calls</code> that tracks how many times a function has been called. After each call, print the count.</p>
<pre><code class="language-python">@count_calls
def say_hello():
    print("Hello!")

say_hello()
# Hello!
# say_hello has been called 1 time(s)

say_hello()
# Hello!
# say_hello has been called 2 time(s)

say_hello()
# Hello!
# say_hello has been called 3 time(s)
</code></pre>
<p><strong>What this tests</strong>: Can you maintain state across function calls using function attributes?</p>
<p><strong>Hint</strong>: Functions are objects, so you can attach attributes to them. Try <code>wrapper.calls = 0</code> to initialize a counter on the wrapper function itself. This counter will persist between calls because it lives on the function object, not inside the function body.</p>
<details>
<summary>Click to reveal solution</summary>
<pre class="not-prose"><code class="language-python">from functools import wraps
</code></pre><p><code>def count_calls(func):<br />wraps(func)<br />def wrapper(*args, **kwargs):<br />wrapper.calls += 1<br />result = func(*args, **kwargs)<br />print(f"{func.name} has been called {wrapper.calls} time(s)")<br />return result<br />wrapper.calls = 0<br />return wrapper</code></p><p><code>wrapper.calls</code> is set to <code>0</code> once when the decorator is applied. Each time <code>wrapper()</code> runs, it increments the counter. The counter lives on the function object, not as a local variable, so it persists between calls.</p><p></p>
</details>

<hr />
<h2>Exercise 5: A Before-and-After decorator</h2>
<p><strong>Task</strong>: Write a decorator called <code>surround</code> that adds a line of dashes before and after the function's output.</p>
<pre><code class="language-python">@surround
def introduce(name, job):
    return f"Hi, I'm {name} and I work as a {job}."

print(introduce("Moussa", "Software Engineer"))
# --------------------
# Hi, I'm Moussa and I work as a Software Engineer.
# --------------------
</code></pre>
<p><strong>What this tests</strong>: Can you add behavior before and after a function call?</p>
<p><strong>Design question</strong>: Should you use <code>print()</code> for the dashes or build them into the return value? Think about which approach would compose better with other decorators.</p>
<details>
<summary>Click to reveal solution</summary>
<pre class="not-prose"><code class="language-python"># Side-effect version (simpler but doesn't compose well):
def surround(func):
    def wrapper(*args, **kwargs):
        print("-" * 20)
        result = func(*args, **kwargs)
        print("-" * 20)
        return result
    return wrapper
</code></pre><h1><code>Return-value version (composes well with other decorators):</code></h1><p><code>def surround(func):<br />def wrapper(*args, **kwargs):<br />result = func(*args, **kwargs)<br />return f"{'-' * 20}\n{result}\n{'-' * 20}"<br />return wrapper</code></p><p>The return-value version is better design. If you stack this with another decorator that transforms return values, everything works smoothly. The side-effect version's dashes can't be intercepted by other decorators.</p><p></p>
</details>

<hr />
<h2>Exercise 6: Input Validation</h2>
<p><strong>Task</strong>: Write a decorator called <code>positive_only</code> that checks if all arguments passed to a function are positive numbers. If any argument is negative or zero, print an error message and don't call the function.</p>
<pre><code class="language-python">@positive_only
def add(a, b):
    return a + b

print(add(3, 5))    # 8
print(add(-1, 5))   # Error: all arguments must be positive!
                     # None
print(add(2, -4))   # Error: all arguments must be positive!
                     # None
</code></pre>
<p><strong>What this tests</strong>: Can you add conditional logic, running the function only when certain conditions are met?</p>
<details>
<summary>Click to reveal solution</summary>
<pre class="not-prose"><code class="language-python">from functools import wraps
</code></pre><p><code>def positive_only(func):<br />@wraps(func)<br />def wrapper(*args, **kwargs):<br />for arg in args:<br />if arg &lt;= 0:<br />print("Error: all arguments must be positive!")<br />return None<br />return func(*args, **kwargs)<br />return wrapper</code></p><p>The loop checks each argument. If any fails, we return early without ever calling the original function. Only if all checks pass do we forward the call.</p><p></p>
</details>

<hr />
<h2>Exercise 7: Decorators with Arguments - <code>@slow_down(seconds)</code></h2>
<p><strong>Task:</strong> Write a decorator called <code>slow_down</code> that takes a number of seconds and waits that long before calling the function.</p>
<pre><code class="language-python">import time

@slow_down(2)
def greet(name):
    print(f"Hello, {name}!")

greet("Moussa")
# (waits 2 seconds)
# Hello, Moussa!
</code></pre>
<p><strong>What this tests:</strong> Can you add the extra layer needed for decorator arguments?</p>
<p><strong>Remember:</strong> <code>@slow_down(2)</code> means Python calls <code>slow_down(2)</code> first, which must return a decorator. So you need three layers: the outer function takes the argument, the middle function takes the function, and the inner function replaces the function.</p>
<details>
<summary>Click to reveal solution</summary>
<pre class="not-prose"><code class="language-python">import time
from functools import wraps
</code></pre><p><code>def slow_down(seconds):<br />def decorator(func):<br />@wraps(func)<br />def wrapper(*args, **kwargs):<br />time.sleep(seconds)<br />return func(*args, **kwargs)<br />return wrapper<br />return decorator</code></p><p>Three layers: <code>slow_down(seconds)</code> → <code>decorator(func)</code> → <code>wrapper(*args, **kwargs)</code>. Each layer remembers the values from the layer above via closures.</p><p></p>
</details>

<hr />
<h2>Exercise 8: Decorator with Arguments - <code>@repeat(n)</code></h2>
<p><strong>Task</strong>: Write a <code>repeat</code> decorator that takes a number <code>n</code> and runs the function <code>n</code> times. It should return the result of the last call.</p>
<pre><code class="language-python">@repeat(4)
def say_hi(name):
    print(f"Hi, {name}!")

say_hi("Moussa")
# Hi, Moussa!
# Hi, Moussa!
# Hi, Moussa!
# Hi, Moussa!
</code></pre>
<p><strong>What this tests</strong>: Combining decorator arguments with loop logic.</p>
<div>
<div>⚠</div>
<div>Be careful where you place the <code>return</code> statement. A <code>return</code> inside a <code>for</code> loop exits the function immediately on the first iteration, the loop won't continue.</div>
</div>

<details>
<summary>Click to reveal solution</summary>
<pre class="not-prose"><code class="language-python">def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(n):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator</code></pre><p>Two important details:</p><ol><li><p><code>result = None</code> before the loop handles the edge case where <code>n</code> is 0</p></li><li><p><code>return result</code> is <em>after</em> the loop, not inside it. Putting <code>return</code> inside the loop would exit on the first iteration.</p></li></ol>
</details>

<hr />
<h2>Exercise 9: Stacking Decorators</h2>
<p><strong>Task</strong>: Using the <code>shout</code> decorator from Exercise 3 and the <code>surround</code> decorator from Exercise 5, predict the output of this code <strong>before running it</strong>.</p>
<pre><code class="language-python">@surround
@shout
def greet(name):
    return f"hello, {name}"

result = greet("Moussa")
print(result)
</code></pre>
<p>Then swap the order:</p>
<pre><code class="language-python">@shout
@surround
def greet(name):
    return f"hello, {name}"

result = greet("Moussa")
print(result)
</code></pre>
<p><strong>Question:</strong> Why are the outputs different? Does one of them produce unexpected results?</p>
<p><strong>What this tests</strong>: Do you understand the execution order of stacked decorators?</p>
<details>
<summary>Click to reveal solution</summary>
<p><strong>With the side-effect version of </strong><code>surround</code><strong>:</strong></p><p>First version (<code>@surround</code> on top of <code>@shout</code>):</p><pre class="not-prose"><code class="language-plaintext">--------------------
--------------------
HELLO, MOUSSA</code></pre><p>The dashes print as side effects (immediately), while the string travels silently through the return chain. By the time <code>print(result)</code> runs, both decorators are done and only the final string appears.</p><p>Second version (<code>@shout</code> on top of <code>@surround</code>): The same visual issue, dashes appear separately from the content.</p><p></p><p><strong>With the return-value version of </strong><code>surround</code><strong>:</strong></p><p>First version (<code>@surround</code> on top of <code>@shout</code>):</p><pre class="not-prose"><code class="language-plaintext">--------------------
HELLO, MOUSSA
--------------------</code></pre><p>Second version (<code>@shout</code> on top of <code>@surround</code>):</p><pre class="not-prose"><code class="language-plaintext">--------------------
HELLO, MOUSSA
--------------------</code></pre><p>(The dashes get uppercased too, but <code>-</code> has no uppercase, so they look the same.)</p><p><strong>The lesson</strong>: Decorators apply bottom-up. The bottom one wraps first. Order matters, especially when mixing side effect and return values. Prefer the return-value approach for better composition.</p>
</details>

<hr />
<h2>Exercise 10: The Boss Challenge - Build a Cache</h2>
<p><strong>Task</strong>: Write a decorator called <code>cache</code> that remembers the results of previous function calls. If the function is called again with the same arguments, return the saved result instead of running the function again.</p>
<pre><code class="language-python">import time

@cache
def slow_add(a, b):
    time.sleep(2)  # pretend this is expensive
    return a + b

print(slow_add(2, 3))  # (waits 2 seconds) → 5
print(slow_add(2, 3))  # (instant!) → 5
print(slow_add(1, 1))  # (waits 2 seconds) → 2
print(slow_add(1, 1))  # (instant!) → 2
</code></pre>
<p><strong>What this tests</strong>: Maintaining state (a dictionary of previous results) across calls, and using tuple arguments as dictionary keys.</p>
<div>
<div>💡</div>
<div>Use dictionary to store results. The key can be <code>args</code> since tuples are hashable. Store the dictionary as an attribute on the wrapper function, just like the counter in Exercise 4.</div>
</div>

<p><strong>Important</strong>: The decorator should return the raw value (like <code>5</code>), not a formatted string (like <code>"(cached) -&gt; 5"</code>). The caller shouldn't know or care that caching is happening.</p>
<details>
<summary>Click to reveal solution</summary>
<pre class="not-prose"><code class="language-python">def cache(func):
    def wrapper(*args):
        if args in wrapper.memory:
            return wrapper.memory[args]
        result = func(*args)
        wrapper.memory[args] = result
        return result
    wrapper.memory = {}
    return wrapper</code></pre><p><code>wrapper.memory</code> is a dictionary that maps argument tuples to results. On each call, the wrapper checks if it's seen these arguments before. If yes, it returns the saved result without calling the original function</p><div class="editor-callout"><div class="callout-emoji">💡</div><div class="callout-text">This is the same concept behind Python's built-in <code>functools.lru_cache</code>. You just built a simplified version from scratch.</div></div>
</details>

<hr />
<h2>How Did you Do?</h2>
<p>If you solved Exercises 1-6 without peeking, you've got a solid grasp of decorator fundamentals. If you also got 7-8, you understand decorators arguments. And if you nailed 9-10, you're ready to use decorators confidently in real projects.</p>
<p>The most common traps to watch for:</p>
<ul>
<li><p><strong>Not using</strong> <code>*args</code>, and <code>**kwargs</code> in the wrapper. It makes your decorator too rigid.</p>
</li>
<li><p><strong>Forgetting</strong> <code>return result</code> in the wrapper. Your values silently become <code>None</code>.</p>
</li>
<li><p><strong>Putting</strong> <code>return</code> <strong>inside a loop</strong> when you want the loop to complete. It exits on the first iteration.</p>
</li>
<li><p><strong>Printing instead of returning</strong> from the wrapper. You values go to the screen instead of to the caller.</p>
</li>
</ul>
<hr />
<h2>Where to Go from Here</h2>
<p>You now have a complete understanding of Python decorators. Some directions to explore next:</p>
<ul>
<li><p><code>functools.wraps</code>: always use it to preserve function metadata</p>
</li>
<li><p><strong>Class-based decorators</strong>: using <code>__call__</code> to make objects behave like decorators</p>
</li>
<li><p><code>functools.lru_cache</code>: the production-grade version of your Exercise 10</p>
</li>
<li><p><code>@property</code>: a decorator that turns methods into computed attributes</p>
</li>
<li><p><strong>Framework decorators</strong>: dig into how Flask's <code>@app.route()</code> or pytest's <code>@fixture</code> work internally</p>
</li>
</ul>
<p>The foundation you've built here will make all of these feel approachable.</p>
<p>Thank you for reading 🙂.</p>
<hr />
<blockquote>
<p>This is Part 4 of my Python Decorators series. The series builds one concept at a time, and each article assumes you've read the ones before it. Start from Part 1 if you haven't already.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Advanced Python Decorators Patterns: Arguments and Stacking]]></title><description><![CDATA[You've learned how decorators work. You've survived the print vs return trap. Now it's time for the patterns you'll actually encounter in the wild: decorators that take arguments and decorators that s]]></description><link>https://blog.moussaamzat.dev/python-decorators-arguments-and-stacking</link><guid isPermaLink="true">https://blog.moussaamzat.dev/python-decorators-arguments-and-stacking</guid><category><![CDATA[Python]]></category><category><![CDATA[decorators]]></category><category><![CDATA[Advanced Python]]></category><category><![CDATA[design patterns]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Moussa Kalam AMZAT]]></dc:creator><pubDate>Wed, 11 Mar 2026 21:17:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6992248b670db28ecd344dea/0cbb1aa2-15ba-48cb-ac1a-ba721f91b20a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You've learned how decorators work. You've survived the <code>print</code> vs <code>return</code> trap. Now it's time for the patterns you'll actually encounter in the wild: decorators that take arguments and decorators that stacked on top of each other.</p>
<p>If you've ever wondered how FastAPI's <code>@app.get("/shipments")</code> work under the hood, this is the article for you.</p>
<hr />
<h2>The Problem with Simple Decorators</h2>
<p>Our decorators so far have all looked like this:</p>
<pre><code class="language-python">@my_decorator
def some_function():
    pass
</code></pre>
<p>The decorator receives the function, wraps it, and returns the wrapper. Clean and simple.</p>
<p>But what if you want a decorator that repeats a function a configurable number of times? You want to use it like this:</p>
<pre><code class="language-python">@repeat(3)
def say_hello():
    print("Hello!")

say_hello()
# Hello!
# Hello!
# Hello!
</code></pre>
<p>And on another function:</p>
<pre><code class="language-python">@repeat(5)
def say_goodbye():
    print("Goodbye!")
</code></pre>
<p>Same decorator, different configuration. How do we build this?</p>
<hr />
<h2>Your First Instinct Is Wrong (And That's OK)</h2>
<p>Your first thought might be: "Just add the number as another parameter."</p>
<pre><code class="language-python">def repeat(func, n):  # &lt;-- this won't work with @
    def wrapper(*args, **kwargs):
        for _ in range(n):
            result = func(*args, **kwargs)
        return result
    return wrapper
</code></pre>
<p>But this breaks with the <code>@</code> syntax. When Python sees <code>@repeat(3)</code>, it calls <code>@repeat(3)</code> <strong>before</strong> it knows about the function. So <code>repeat</code> receives <code>3</code>, not a function. There's no way to pass both the number and the function at the same time.</p>
<blockquote>
<p><code>@repeat</code> and <code>@repeat(3)</code> are not the same pattern. The first expect <code>repeat</code> itself to be a decorator. The second expect <code>repeat(3)</code> to return a decorator.</p>
</blockquote>
<hr />
<h2>Understanding What <code>@repeat(3)</code> Actually Does</h2>
<p>Let's slow down and think about what Python does when it sees:</p>
<pre><code class="language-python">@repeat(3)
def say_hello():
    print("Hello!")
</code></pre>
<p>It evaluates this in <strong>two steps</strong>:</p>
<ol>
<li><p><strong>First</strong>, it calls <code>repeat(3)</code>. This must return <strong>a decorator</strong>.</p>
</li>
<li><p><strong>Then</strong>, it applies that decorator to <code>say_hello</code>.</p>
</li>
</ol>
<p>Written out explicitly:</p>
<pre><code class="language-python"># Step 1: repeat(3) returns a decorator
decorator = repeat(3)

# Step 2: that decorator wraps say_hello
say_hello = decorator(say_hello)
</code></pre>
<p>Or in one line: <code>say_hello = repeat(3)(say_hello)</code>.</p>
<p>This means <code>repeat</code> is not the decorator itself anymore. It's a <strong>function that builds and returns a decorator</strong>. One extra layer.</p>
<blockquote>
<p>This wrapping happens once, when Python executes the <code>def</code> statement. After that, the name <code>say_hello</code> no longer refers to the original function. It refers to the wrapper returned by the decorator.</p>
</blockquote>
<hr />
<h2>Building It Step by Step</h2>
<p>Let's start with something we already know, a decorator with the number hardcoded:</p>
<pre><code class="language-python">from functools import wraps

def repeat_three(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = None
        for _ in range(3):
            result = func(*args, **kwargs)
        return result
    return wrapper


@repeat_three
def say_hello():
    print("Hello!")
</code></pre>
<div>
<div>💡</div>
<div>Remember from the first article in this series: You will usually use <code>functools.wraps(func)</code> on the wrapper so the decorated function keeps metadata like its original name and docstring.</div>
</div>

<p>This works, but <code>3</code> is baked in. To make it configurable, wrap the whole thing in a function that takes <code>n</code>:</p>
<pre><code class="language-python">from functools import wraps

def repeat(n):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(n):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator
</code></pre>
<p>Three layers. Read them from the outside in:</p>
<ul>
<li><p><code>repeat(n)</code> takes your configuration argument</p>
</li>
<li><p><code>decorator(func)</code> takes the function being decorated (this is the actual decorator)</p>
</li>
<li><p><code>wrapper(*args, **kwargs)</code> replaces the original function</p>
</li>
</ul>
<p>That's it. Now it works with any number:</p>
<pre><code class="language-python">@repeat(3)
def say_hello():
    print("Hello!")

@repeat(5)
def say_goodbye():
    print("Goodbye!")

say_hello()
# Hello!
# Hello!
# Hello!

say_goodbye()
# Goodbye!  (x5)
</code></pre>
<blockquote>
<p>Notice that <code>repeat</code> returns the result from the final execution of the function. If the function returns different values on each call, only the last one is preserved.</p>
</blockquote>
<div>
<div>💡</div>
<div>If <code>n</code> were <code>0</code>, this implementation would return <code>None</code>, so in real code you may want to validate that <code>n &gt;= 1</code>.</div>
</div>

<hr />
<h2>Let's Trace Through the Execution</h2>
<p>When Python sees <code>@repeat(3)</code> above <code>say_hello</code>, here's exactly what happens:</p>
<ol>
<li><p>Python calls <code>repeat(3)</code></p>
</li>
<li><p><code>repeat(3)</code> returns the <code>decorator</code> function, with <code>n=3</code> remembered (closure)</p>
</li>
<li><p>Python applies that decorator to <code>say_hello</code>: <code>decorator(say_hello)</code></p>
</li>
<li><p><code>decorator(say_hello)</code> returns the <code>wrapper</code> function, with <code>func=say_hello</code> remembered</p>
</li>
<li><p><code>say_hello</code> is now replaced by <code>wrapper</code></p>
</li>
<li><p>When you call <code>say_hello()</code>, you're calling <code>wrapper()</code></p>
</li>
<li><p><code>wrapper</code> runs the original <code>say_hello</code> 3 times (because <code>n=3</code> is remembered)</p>
</li>
</ol>
<p>Closures are doing all the heavy lifting here. Each layer remembers the values from the layer above it.</p>
<blockquote>
<p>A closure is a function that remembers variables from the scope where it was created, even after that outer function has finished running.</p>
</blockquote>
<hr />
<h2>Practical Example: A Retry Decorator</h2>
<p>Here's a decorator that retries a function if it throws an exception:</p>
<pre><code class="language-python">from functools import wraps
import time

def retry(max_attempts):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Attempt {attempt} failed: {e}")
                    if attempt &lt; max_attempts:
                        print("Retrying...")
                        time.sleep(1)
                    else:
                        print("All attempts failed!")
                        raise # Re-raise the exception if all attempts exhausted
        return wrapper
    return decorator

@retry(3)
def connect_to_server():
    print("Trying to connect...")
    raise ConnectionError("Server is down")

connect_to_server()
# Trying to connect...
# Attempt 1 failed: Server is down
# Retrying...
# Trying to connect...
# Attempt 2 failed: Server is down
# Retrying...
# Trying to connect...
# Attempt 3 failed: Server is down
# All attempts failed!
</code></pre>
<p>Notice how the <code>return</code> inside the <code>try</code> block exits the wrapper immediately on success. The loop only continues if an exception is caught.</p>
<blockquote>
<p>This is a simplified example for learning. Real retry logic usually catches specific exceptions, may use exponential backoff, and often avoids retrying on every kind of failure.</p>
</blockquote>
<hr />
<h2>Practical Example: Role-Based Access Control</h2>
<pre><code class="language-python">from functools import wraps
 
def require_role(role):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            current_user_role = "editor"  # imagine this from a database
            if current_user_role != role:
                print(f"Access denied! You need '{role}' role.")
                return None
            return func(*args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def delete_user(user_id):
    print(f"User {user_id} deleted!")

@require_role("editor")
def edit_post(post_id):
    print(f"Post {post_id} edited!")

delete_user(42)   # Access denied! You need 'admin' role.
edit_post(7)      # Post 7 edited!
</code></pre>
<div>
<div>⚠</div>
<div>In real code, <code>current_user_role</code> would be injected (e.g, from a request context or a passed argument), not hardcoded. The hardcoded <code>"editor"</code> is just a placeholder to make the demo runnable.</div>
</div>

<p>Same decorator, different configuration. That's the power of decorator arguments.</p>
<hr />
<h2>The Pattern to Remember</h2>
<p><strong>Without arguments -</strong> Two layers:</p>
<pre><code class="language-python">from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper
</code></pre>
<p><strong>With arguments -</strong> Three layers:</p>
<pre><code class="language-python">from functools import wraps

def decorator_factory(arguments):    # takes your settings
    def decorator(func):              # takes the function
        @wraps(func)
        def wrapper(*args, **kwargs):  # replaces the function
            return func(*args, **kwargs)
        return wrapper
    return decorator
</code></pre>
<p>The only difference is one extra outer function that holds your configuration. That's all there is to it.</p>
<hr />
<h2>Stacking Decorators</h2>
<p>You can apply multiple decorators to a single function. They apply <strong>bottom-up</strong>: the bottom decorator wraps the function first, and the top decorator wraps the result.</p>
<pre><code class="language-python">@timer
@repeat(3)
def greet():
    print("Hi!")

# Equivalent to:
decorated = repeat(3)(greet)
greet = timer(decorated)
</code></pre>
<p>Here, <code>repeat(3)</code> wraps <code>greet</code> first, then <code>timer</code> wraps the result. When you call <code>greet()</code>, <code>timer</code>'s wrapper runs first, which calls <code>repeat</code>'s wrapper, which calls the original <code>greet</code> three times.</p>
<blockquote>
<p>The bottom decorator wraps first, but the top decorator's wrapper runs first when the function is called.</p>
</blockquote>
<h3>When Stacking Works Well</h3>
<p>Decorators that transform <strong>return values</strong> compose beautifully together, because each decorator receives the output of the one below it and can modify it before passing it up:</p>
<pre><code class="language-python">from functools import wraps

def add_exclamation(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result + "!"
    return wrapper

def make_uppercase(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

@add_exclamation
@make_uppercase
def greet(name):
    return f"hello, {name}"

print(greet("Moussa"))  # HELLO, MOUSSA!
</code></pre>
<p><code>make_uppercase</code> wraps first, turning the return value to <code>"HELLO, MOUSSA"</code>.</p>
<p>Then <code>add_exclamation</code> wraps that, adding <code>"!"</code> to make <code>"HELLO MOUSSA!"</code>.</p>
<p>Clean, predictable, composable.</p>
<div>
<div>💡</div>
<div>Because each wrapper receives a value and returns a new value, the decorators form a chain where each layer can still influence the final result.</div>
</div>

<h3>When Stacking Gets Tricky</h3>
<p>Decorators that use <strong>side effects</strong> (like <code>print()</code>) don't compose as cleanly. If one decorator prints something and another transforms return values, the printed output can't be intercepted or reordered by the other decorator. The side effects have already escaped the chain.</p>
<p><strong>The general principle</strong>: When you're designing decorators that might be stacked, prefer working with return values over side effects. This gives other decorators in the chain a chance to interact with your output.</p>
<blockquote>
<p><code>print()</code> sends text directly to standard output and returns <code>None</code>, so another decorator cannot "catch" that printed text the way it can catch and modify a normal return value.</p>
</blockquote>
<hr />
<h2>Common Decorator Patterns in the Wild</h2>
<p>Now that you understand the mechanics, you'll start recognizing these patterns everywhere:</p>
<p><strong>FastAPI routes</strong></p>
<pre><code class="language-python">@app.get("/")
def root():
    return {"message": "Welcome to my API"}
</code></pre>
<p><strong>Django authentication</strong></p>
<pre><code class="language-python">@login_required             # simple decorator
def dashboard(request):
    return render(request, "dashboard.html")
</code></pre>
<p><strong>Python builtins</strong></p>
<pre><code class="language-python">class MyClass:
    @staticmethod           # simple decorator
    def utility():
        pass

    @property               # simple decorator
    def name(self):
        return self._name
</code></pre>
<p><strong>Functools caching</strong></p>
<pre><code class="language-python">from functools import lru_cache

@lru_cache(maxsize=128)    # decorator with arguments
def fibonacci(n):
    if n &lt; 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
</code></pre>
<p>They all follow the same principles we've learned. Some take arguments (three layers), some don't (two layers). But the core pattern is always the same: take a function in, return a wrapped version out.</p>
<hr />
<h2>What's Next?</h2>
<p>You now have the complete picture of Python decorators, from the foundations through the common pitfalls to advanced patterns. In the final part of this series, I've put together 10 hands-on exercises that will test and solidify everything you've learned.</p>
<p>Subscribe to my newsletter if you don't want to miss any updates.<br />Thank you for reading 🙂.</p>
<hr />
<blockquote>
<p><em>This is Part 3 of my Python Decorator series. Check out</em> <a href="https://blog.moussaamzat.dev/understanding-python-decorators-from-scratch"><em>Part 1: Understanding Decorators From Scratch</em></a> <em>for foundations and</em> <a href="https://blog.moussaamzat.dev/print-vs-return-trap-python-decorators"><em>Part 2: The</em> Print vs Return Trap</a> for the most common pitfall.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[The Print vs Return Trap That Silently Breaks Your Python Decorators]]></title><description><![CDATA[When I was learning about decorators, I kept running into the same frustrating problem: my decorated function kept returning None. The decorator looked right. The function looked right. But the values]]></description><link>https://blog.moussaamzat.dev/print-vs-return-trap-python-decorators</link><guid isPermaLink="true">https://blog.moussaamzat.dev/print-vs-return-trap-python-decorators</guid><category><![CDATA[Python]]></category><category><![CDATA[decorators]]></category><category><![CDATA[debugging]]></category><category><![CDATA[python tips]]></category><category><![CDATA[common mistakes]]></category><dc:creator><![CDATA[Moussa Kalam AMZAT]]></dc:creator><pubDate>Sun, 08 Mar 2026 21:05:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6992248b670db28ecd344dea/f664c892-4546-438f-809d-28bb7ef6c412.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I was learning about decorators, I kept running into the same frustrating problem: my decorated function kept returning <code>None</code>. The decorator looked right. The function looked right. But the values just... vanished.</p>
<p>It took me an embarrassingly long time to realized I was confusing <code>print</code> with <code>return</code>. And I don't think I'm alone as this is the single most common bug when learning decorators, especially you're coming from JavaScript where <code>console.log</code> is your best friend.</p>
<p>Let me save you the headache.</p>
<hr />
<h2>The Difference That Changes Everything</h2>
<p>Let's get this absolutely clear before we touch decorators.</p>
<p><code>print()</code> <strong>sends text to the screen. That's it.</strong> It's a one-way trip. The value goes to the terminal and vanishes from the program's perspective. You can't catch it, store it, or pass it along.</p>
<p><code>return</code> <strong>silently passes a value back to the caller.</strong> Nothing appears on the screen. The value stays inside the program where it can be stored, transformed, or used however the caller wants.</p>
<pre><code class="language-python">def version_a():
    print("hello")     # shows on screen, returns None

def version_b():
    return "hello"     # shows nothing, gives value to caller
</code></pre>
<p>These two functions look almost identical, but they behave completely differently:</p>
<pre><code class="language-python"># Using version_a:
x = version_a()         # "hello" appears on screen
print(x)                # None  &lt;-- the value is GONE
print(type(x))          # &lt;class 'NoneType'&gt;

# Using version_b:
y = version_b()         # nothing appears on screen
print(y)                # hello  &lt;-- the value is HERE
print(y.upper())        # HELLO  &lt;-- we can transform it
print(len(y))           # 5     &lt;-- we can use it
</code></pre>
<p>Think of it this way: <code>print()</code> is like shouting something in a room. Everyone hears it, but you can't unsay it or hand it to someone specific. <code>return</code> is like writing something on a note and handing it to the person who asked. They can read it, pass it on, or put it in their pocket for later.</p>
<hr />
<h2>Why This Breaks Decorators</h2>
<p>A decorator's wrapper function is a middleman. It sits between the caller and the original function. Whatever the original function returns, the wrapper has to catch and pass along. If it doesn't, the value disappears.</p>
<p>Here is the most common mistake:</p>
<h3>Mistake 1: Forgetting to Return</h3>
<pre><code class="language-python">from functools import wraps

def log_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        func(*args, **kwargs)   # called, but result THROWN AWAY
        print("Done")
    return wrapper


@log_calls
def add(a, b):
    return a + b

result = add(2, 3)
print(result)
# Calling add
# Done
# None    &lt;--- WHERE IS THE 5?!
</code></pre>
<p>The function <code>add(2, 3)</code> computed <code>5</code> and returned it to the wrapper. But the wrapper caught it in mid-air and dropped it. The wrapper itself has no <code>return</code> statement so it returns <code>None</code>. The <code>5</code> simply ceased to exist.</p>
<p>The fix is two lines:</p>
<pre><code class="language-python">from functools import wraps

def log_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)  # CATCH the value
        print("Done")
        return result                    # PASS IT ALONG
    return wrapper
</code></pre>
<h3>Mistake 2: Printing Instead of Returning</h3>
<p>This one is sneakier because if <strong>feels</strong> like it's working:</p>
<pre><code class="language-python">from functools import wraps

def cache(func):
    @wraps(func)
    def wrapper(*args):
        if args in wrapper.memory:
            print(f"Cached: {wrapper.memory[args]}")    # prints to screen!
        else:
            result = func(*args)
            wrapper.memory[args] = result
            print(f"Computed: {result}")    # prints to screen!

    wrapper.memory = {} # Functions are objects 😉
    return wrapper


@cache
def square(n):
    return n * n

answer = square(5)
print(answer)
# Computed: 25
# None          &lt;--- the answer never arrived!

# Even worse:
total = square(5) + square(3)
# TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'
</code></pre>
<p>You see <code>25</code> on the screen, so it looks like it's working. But the value went to the terminal, not to the caller. The variable <code>answer</code> holds <code>None</code>, not <code>25</code>. The wrapper printed the result instead of returning it. It shouted the answer across the room instead of handing it over.</p>
<h3>Mistake 3: Returning Formatted Strings Instead of Raw Values</h3>
<p>Another trap I fell into was returning descriptive strings from the wrapper:</p>
<pre><code class="language-python">from functools import wraps

def cache(func):
    @wraps(func)
    def wrapper(*args):
        if args in wrapper.memory:
            return f"(instant!) -&gt; {wrapper.memory[args]}"
        result = func(*args)
        wrapper.memory[args] = result
        return f"(computed) -&gt; {result}"
    wrapper.memory = {} # A function is an object
    return wrapper

@cache
def add(a, b):
    return a + b

result = add(2, 3)
print(result)          # (computed) -&gt; 5
print(result + 10)     # TypeError! Can't add string + int
</code></pre>
<p>The decorator <strong>changed the function's return type</strong> from a number to a string. Any code expecting a number back from <code>add</code> will break. A decorator should be invisible, the caller shouldn't be able to tell the function is decorated.</p>
<hr />
<h2>The Correct Pattern</h2>
<p>Always capture the return value and always return it:</p>
<pre><code class="language-python">def my_decorator(func):
    def wrapper(*args, **kwargs):
        # do whatever you want before
        result = func(*args, **kwargs)    # CATCH the value
        # do whatever you want after
        return result                      # PASS IT ALONG
    return wrapper
</code></pre>
<p>Two lines. That's the difference between a decorator that works and one that silently eats your data.</p>
<hr />
<h2>"But What If My Function Doesn't Return Anything?"</h2>
<p>Great question! Some functions just <strong>do</strong> things without returning a value:</p>
<pre><code class="language-python">def say_hello():
    print("Hello!")    # prints, returns None implicitly
</code></pre>
<p>If you wrap this function, <code>result</code> will be <code>None</code>. And returning <code>None</code> is perfectly harmless:</p>
<pre><code class="language-python">@log_calls
def say_hello():
    print("Hello!")    # no return statement

say_hello()
# Calling say_hello
# Hello!
# Done

# result is None, but nobody cares — it works perfectly
</code></pre>
<p>This is exactly why you should <strong>always return the result</strong>, even if you think the function won't return anything:</p>
<ul>
<li><p>If the function returns something, you've preserved it. ✅</p>
</li>
<li><p>If the function returns nothing, you return <code>None</code>, which is harmless. ✅</p>
</li>
</ul>
<p>You're safer either way.</p>
<div>
<div>💡</div>
<div>Making <code>return result</code> your reflex in every wrapper means you never have to think about it.</div>
</div>

<hr />
<h2>The Delivery Person Analogy</h2>
<p>I find this mental model helpful:</p>
<p>Your wrapper function is a delivery person standing at a door. They knock (call the original function), and the person inside either hands them a package (return value) or just waves hello (no return value).</p>
<ul>
<li><p><strong>If the delivery person always takes whatever is offered and passes it along</strong>, the package arrives safely, and the wave is harmless. Everything works.</p>
</li>
<li><p><strong>If the delivery person shouts out the package contents</strong> (<code>print</code>), the neighborhood hears it, but the actual recipient gets nothing. The package is "delivered" to the air.</p>
</li>
<li><p><strong>If the delivery person ignores what's handed to them</strong> (no <code>return</code>), the package sits on the doorstep and rots. The recipient never knows it existed.</p>
</li>
</ul>
<p>Always take the package. Always deliver it.</p>
<hr />
<h2>How This Affects Stacking Decorators</h2>
<p>The <code>print</code> vs <code>return</code> distinction also explains a confusing behavior when you stack multiple decorators. Consider these two:</p>
<pre><code class="language-python">from functools import wraps

def shout(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()          # transforms the RETURN VALUE
    return wrapper

def surround(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("-" * 20)                # SIDE EFFECT - escapes immediately
        result = func(*args, **kwargs)
        print("-" * 20)                # SIDE EFFECT - escapes immediately
        return result
</code></pre>
<p><code>shout</code> works with the return values. <code>surround</code> works with side effect (<code>print</code>). Watch what happens when you stack them:</p>
<pre><code class="language-python">@shout
@surround
def greet(name):
    return f"Hello, {name}!"

print(greet("Moussa"))
</code></pre>
<p>You might expect:</p>
<pre><code class="language-plaintext">--------------------
HELLO, MOUSSA!
--------------------
</code></pre>
<p>But what you actually get is:</p>
<pre><code class="language-plaintext">--------------------
--------------------
HELLO, MOUSSA!
</code></pre>
<p>The dashes aren't surrounding anything. They appear back-to-back at the top, and the uppercase greeting appears separately at the bottom.</p>
<p>Here's why. When <code>greet("Moussa")</code> is called, <code>shout</code>'s wrapper runs first (outer decorator runs first). It calls <code>surround</code>'s wrapper, which prints the first line of dashes (side effect - immediately on screen), calls the original <code>greet</code> which returns <code>"Hello, Moussa!"</code> silently, prints the second line of dashes (another side effect - immediately on screen), and returns the string. Back in <code>shout</code>'s wrapper, it receives the string and uppercases it to <code>HELLO, MOUSSA!</code>, then returns it. Finally your <code>print()</code> call in the main code displays it.</p>
<p>The key: between the two lines of dashes, the string was being <strong>passed around silently</strong> as a return value. No <code>print()</code> call happened for it between the dashes. The only things that printed between "calling <code>surround</code>'s wrapper" and "<code>surround</code>'s wrapper returning) were the two lines of dashes, back to back.</p>
<div>
<div>💡</div>
<div><strong><u>The lesson</u></strong>: Decorators that transform values compose beautifully together. Decorators that use side effects like <code>print()</code> don't, because side effects escape the decorator chain immediately and can't be controlled by other decorators.</div>
</div>

<p>The fix is to make <code>surround</code> use return values instead:</p>
<pre><code class="language-python">def surround(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return f"{'—' * 20}\n{result}\n{'—' * 20}"
    return wrapper
</code></pre>
<p>Now both decorators work with return values, and stacking produces the expected result regardless of order.</p>
<hr />
<h2>The Rules to Live By</h2>
<ol>
<li><p><code>print()</code> <strong>sends a value to the screen</strong>. It's a one-way trip. The value is gone from the program.</p>
</li>
<li><p><code>return</code> <strong>sends a value back to the caller</strong>. It stays in the program and can be used further.</p>
</li>
<li><p><strong>In a decorator wrapper, always use</strong> <code>result = func(*args, **kwargs)</code> <strong>followed by</strong> <code>return result</code>. This is the universal safe pattern.</p>
</li>
<li><p><strong>A decorator should be invisible</strong>. The caller shouldn't be able to tell the function is decorated. Don't change return types. Don't print inside the wrapper when you should be returning.</p>
</li>
<li><p><strong>Decorators that work with return values stack well</strong>. Decorators that use side effects don't. Prefer return values when designing decorators meant for composition.</p>
</li>
<li><p><strong>When in doubt, return.</strong> It's always safe.</p>
</li>
</ol>
<hr />
<h2>What's Next?</h2>
<p>In the next article, we'll tackle decorators that accept their own arguments, patterns like <code>@repeat(3)</code> and <code>@retry(5)</code>. We'll also learn how to stack decorators effectively.</p>
<blockquote>
<p><em>This is Part 2 of my Python Decorators series. Check out</em> <a href="https://blog.moussaamzat.dev/understanding-python-decorators-from-scratch"><em>Part 1: Understanding Decorators From Scratch for the foundations</em></a><em>, and stay tuned for Part 3 on advanced patterns.</em></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[How Python Handles Unknown Arguments: A Deep Dive into *args and **kwargs]]></title><description><![CDATA[If you've spent any time reading Python code, you've probably come across function signatures that look like this:
def some_function(*args, **kwargs):
    pass

And wondered, what are those asterisks ]]></description><link>https://blog.moussaamzat.dev/python-args-kwargs-complete-guide</link><guid isPermaLink="true">https://blog.moussaamzat.dev/python-args-kwargs-complete-guide</guid><category><![CDATA[Python]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Moussa Kalam AMZAT]]></dc:creator><pubDate>Fri, 06 Mar 2026 13:14:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6992248b670db28ecd344dea/db9c16a6-67e0-4d24-84c6-943dd30c8630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you've spent any time reading Python code, you've probably come across function signatures that look like this:</p>
<pre><code class="language-python">def some_function(*args, **kwargs):
    pass
</code></pre>
<p>And wondered, what are those asterisks doing there? What are <code>*args</code> and <code>**kwargs</code>? Why does every Python codebase seem to have them?</p>
<p>By the end of the article, you'll have a complete understanding of what they are, how they work, and more importantly <strong>why</strong> they exist. We'll build up from the problem they solve, through the mechanics, all the way to a real-world decorator you can use in your own projects.</p>
<hr />
<h2>Before We Begin: Two Types of Arguments</h2>
<p>Python functions accepts two distinct types of arguments. Understanding the difference is essential before diving into <code>*args</code> and <code>**kwargs</code>.</p>
<h3>Positional Arguments</h3>
<p>A positional argument is passed by <strong>position</strong>. In other words, its meaning is determined by where it appears in the function call:</p>
<pre><code class="language-python">def create_user(name, age, city):
    print(f"{name}, {age}, {city}")

create_user("Moussa", 27, "Porto-Novo")
# Moussa, 27, Porto-Novo
</code></pre>
<p>Here, <code>"Moussa"</code> maps to <code>name</code>, <code>27</code> maps to <code>age</code>, and <code>Porto-Novo</code> maps to <code>city</code>, purely based on their <strong>order</strong>,</p>
<p>Swap them and the meaning changes entirely:</p>
<pre><code class="language-python">create_user("Porto-Novo", 27, "Moussa")
# Porto-Novo, 27, Moussa (WRONG)
</code></pre>
<h3>Keyword Arguments</h3>
<p>A keyword argument is a passed by <strong>name</strong>. You explicitly state which parameter receives which value, so order no longer matters:</p>
<pre><code class="language-python">create_user(city="Porto-Novo", name="Moussa", age=27)
# Moussa, 27, Porto-Novo (correct, despite different order)
</code></pre>
<p>The same function accepts both styles. Python handles they differently under the hook, and that distinction is exactly what <code>*args</code> and <code>**kwargs</code> are built on.</p>
<div>
<div>💡</div>
<div><strong>Parameter</strong> is the variable name defined in the function signature. <strong>Argument</strong> is the actual value passed when calling the function.</div>
</div>

<pre><code class="language-python">def greet(name):   # 'name' is a PARAMETER
    print(name)

greet("Moussa")    # "Moussa" is an ARGUMENT
</code></pre>
<hr />
<h2>The Problem: Functions With Fixed Arguments</h2>
<p>Let's start with something familiar. Here's a basic Python function:</p>
<pre><code class="language-python">def add(a, b):
    return a + b

print(add(1, 2))  # 3
</code></pre>
<p>This works perfectly, as long as you always have exactly two numbers to add. But what if you want to add three? Or five? Or you don't know in advance how many you'll have?</p>
<pre><code class="language-python">print(add(1, 2, 3))
# TypeError: add() takes 2 positional arguments but 3 were given
</code></pre>
<p>Python refuses. The function signature is a "contract": exactly two arguments. No more, no less.</p>
<p>You could try to work around it by writing separate functions:</p>
<pre><code class="language-python">def add_two(a, b):
    return a + b

def add_three(a, b, c):
    return a + b + c
</code></pre>
<p>But this obviously doesn't scale. You'd need a new function for every possible number of arguments. There has to be a better way. Don't you think so 😀? That's exactly what <code>*args</code> was designed to solve.</p>
<hr />
<h2>Part 1: <code>*args</code> - Collecting Unlimited Positional Arguments</h2>
<p>Here's the solution:</p>
<pre><code class="language-python">def add(*args):
    return sum(args)

print(add(1, 2))           # 3
print(add(1, 2, 3))        # 6
print(add(1, 2, 3, 4, 5))  # 15
</code></pre>
<p>The <code>*</code> in the function signature tells Python: <em><strong>collect all positional arguments into a single tuple</strong></em>. Inside the function, <code>args</code> is just a regular tuple. You can iterate it, index it, pass it to <code>sum()</code>, or anything else you'd do with a normal tuple.</p>
<h3>The <code>*</code> Is the Mechanism, Not the Name</h3>
<p>This is important: the <code>*</code> is what does the work, not the word <code>args</code>. You can call it anything:</p>
<pre><code class="language-python">def add(*numbers):
    return sum(numbers)

def add(*values):
    return sum(values)
</code></pre>
<p>Both work identically. The name <code>args</code> is a widely adopted convention. You'll see it in almost every Python codebase, but it's just a name.</p>
<h3>Mixing Fixed Arguments With <code>*args</code></h3>
<p>You can combine fixed arguments with <code>args</code>. The fixed ones must come first:</p>
<pre><code class="language-python">def greet(greeting, *names):
    for name in names:
        print(f"{greeting}, {name}!")

greet("Hello", "Fabien", "Yves", "John")
# Hello, Fabien!
# Hello, Yves!
# Hello, John!
</code></pre>
<p><code>greeting</code> captures the first argument, and <code>*names</code> collects everything else into a tuple.</p>
<h3>Common Mistake: Putting <code>*args</code> Before Fixed Arguments</h3>
<pre><code class="language-python">def greet(*names, greeting):
    pass

greet("Alice", "Bob", "Hello")
# TypeError: greet() missing 1 required keyword-only argument: 'greeting'
</code></pre>
<p>When <code>*args</code> appears before a named parameter, that named parameter becomes <em><strong>keyword-only</strong></em>. As a result, it must be passed by <strong>name</strong>, not position. This is a common source of confusion. Keep fixed positional arguments before <code>*args</code> and you'll avoid it.</p>
<h3>Mental Model</h3>
<blockquote>
<p><code>*args</code> <strong>= "Pack all positional arguments into a tuple called</strong> <code>args</code><strong>".</strong></p>
</blockquote>
<hr />
<h2>Part 2: <code>**kwargs</code> - Collecting Unlimited Keyword Arguments</h2>
<p>Python functions also accept <em><strong>keyword arguments</strong></em>, arguments passed by name:</p>
<pre><code class="language-python">def create_user(name, age, city):
    print(f"{name}, {age}, {city}")

create_user(name="Moussa", age=27, city="Porto-Novo")
</code></pre>
<p>What if you don't know which keyword argument will be passed in advance? That's where <code>**kwargs</code> comes in:</p>
<pre><code class="language-python">def create_user(**kwargs):
    print(kwargs)

create_user(name="Moussa", age=27, city="Porto-Novo")
# {'name': 'Moussa', 'age': 27, 'city': 'Porto-Novo'}
</code></pre>
<p>The <code>**</code> tells Python: <em><strong>collect all keyword arguments into a dictionary</strong></em>. The argument names become keys, and the values you pass become the dictionary values.</p>
<p>Inside the function, <code>kwargs</code> is just a regular dictionary:</p>
<pre><code class="language-python">def create_user(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

create_user(name="Moussa", age=27, city="Porto-Novo")
# name: Moussa
# age: 27
# city: Porto-Novo
</code></pre>
<h3>Accessing Specific Keys Safely</h3>
<p>When you know which keys you're looking for, use <code>.get()</code> with a default value:</p>
<pre><code class="language-python">def create_server(**kwargs):
    host = kwargs.get("host", "localhost")
    port = kwargs.get("port", 8000)
    debug = kwargs.get("debug", False)
    print(f"Starting {host}:{port} | debug={debug}")

create_server(port=3000)
# Starting localhost:3000 | debug=False
</code></pre>
<div>
<div>💡</div>
<div><code>.get()</code> safely returns the default value if a key is missing, rather than throwing a <code>KeyError</code>. Note that the default value is the second argument you pass to <code>.get()</code></div>
</div>

<h3>Mental Model</h3>
<blockquote>
<p><code>**kwargs</code> = "Pack all keyword arguments into a dictionary called <code>kwargs</code></p>
</blockquote>
<p>Same pattern as <code>*args</code>, but with a different container:</p>
<ul>
<li><p><code>*args</code>: Positional arguments <strong>(tuple)</strong></p>
</li>
<li><p><code>**kwargs</code>: Keyword arguments <strong>(dict)</strong></p>
</li>
</ul>
<hr />
<h2>Part 3: The <code>*</code> Unpack Direction - and <code>**</code> at the Call Site</h2>
<p>Here's the part that trips most people up: the <code>*</code> <em>and</em> <code>**</code> operators work in <em>both directions.</em></p>
<ul>
<li><p>In a <strong>function definition</strong>, they <em>pack</em> arguments in</p>
</li>
<li><p>In a <strong>function call</strong>, they <em>unpack</em> a collection out</p>
</li>
</ul>
<pre><code class="language-python"> def add(a, b, c):
    return a + b + c

numbers = [1, 2, 3]
print(add(*numbers))  # ✅ Unpacks the list into positional arguments → 6

details = {"a": 1, "b": 2, "c": 3}
print(add(**details))  # ✅ Unpacks the dict into keyword arguments → 6
</code></pre>
<p>The <code>*</code> doesn't care whether you give it a list or a tuple. It works on any <a href="https://www.w3schools.com/python/python_iterators.asp">iterable</a>:</p>
<pre><code class="language-python">my_tuple = (1, 2, 3)
my_list = [1, 2, 3]

print(add(*my_tuple))  # ✅ 6
print(add(*my_list))   # ✅ 6
</code></pre>
<div>
<div>💡</div>
<div>An iterable is any object Python can loop over. Under the hook, it is simply an object that implements the <code>__iter__()</code>. method, which returns an <strong>iterator </strong>that yields values one at a time.</div>
</div>

<p>The <code>**</code> operator only works on mappings (objects with key-value pairs). For example, a <code>set</code> has no keys, so Python has no idea which value maps to which argument:</p>
<pre><code class="language-python">my_set = {"Moussa", "Porto-Novo"}
some_function(**my_set)  # ❌ TypeError — sets have no keys
</code></pre>
<h3>The Mental Model</h3>
<table>
<thead>
<tr>
<th>Syntax</th>
<th>Where</th>
<th>What it does</th>
<th>Result</th>
</tr>
</thead>
<tbody><tr>
<td><code>*args</code></td>
<td>Definition</td>
<td>Packs positional args</td>
<td>Tuple</td>
</tr>
<tr>
<td><code>**kwargs</code></td>
<td>Definition</td>
<td>Packs keyword args</td>
<td>Dict</td>
</tr>
<tr>
<td><code>*iterable</code></td>
<td>Call</td>
<td>Unpacks into positional args</td>
<td>Spread</td>
</tr>
<tr>
<td><code>**dict</code></td>
<td>Call</td>
<td>Unpacks into keyword args</td>
<td>Spread</td>
</tr>
</tbody></table>
<p>Same symbol, opposite direction. Once you internalize the table above, the behavior becomes predictable everywhere you encounter it.</p>
<hr />
<h2>Part 4: Combining <code>*args</code> and <code>**kwargs</code></h2>
<p>You can use both in the same function. Python automatically routes each argument to the right place. No name (position) argument attached goes to <code>args</code>, name argument attached goes to <code>kwargs</code>:</p>
<pre><code class="language-python">def describe(*args, **kwargs):
    print("Positional:", args)
    print("Keyword:", kwargs)

describe(1, 2, 3, name="Moussa", city="Porto-Novo")
# Positional: (1, 2, 3)
# Keyword: {'name': 'Moussa', 'city': 'Porto-Novo'}
</code></pre>
<h3>The Order Rule</h3>
<p>When combining everything, Python enforces a strict order:</p>
<pre><code class="language-python">def func(fixed, *args, **kwargs):
    pass
</code></pre>
<ol>
<li><p>Fixed positional arguments first</p>
</li>
<li><p><code>*args</code> second</p>
</li>
<li><p><code>**kwargs</code> last</p>
</li>
</ol>
<p>Breaking this order produces an immediate <code>SyntaxError</code> and Python won't even run the file.</p>
<h3>Empty Defaults</h3>
<p>When no arguments are passed, <code>*args</code> and <code>**kwargs</code> don't become <code>None</code>. They become their empty defaults:</p>
<pre><code class="language-python">def safe_summary(*args, **kwargs):
    print(args)   # ()  ← empty tuple
    print(kwargs) # {}  ← empty dict

safe_summary()
</code></pre>
<p>This matters because code like <code>for item in args</code> still works safely with no arguments. <code>None</code> would crash it.</p>
<hr />
<h2>Part 5: The Wrapper Pattern - Where This Gets Powerful</h2>
<p>The most important real-world use of <code>*args</code> and <code>**kwargs</code> I know is the <strong>wrapper pattern</strong>: forwarding arguments through a function without caring what they are.</p>
<p>Here is the code idea:</p>
<pre><code class="language-python">def log_call(func, *args, **kwargs):
    print(f"Calling {func.__name__}")
    result = func(*args, **kwargs)
    print(f"Done")
    return result

def add(a, b):
    return a + b

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

log_call(add, 1, 2)
log_call(greet, "Moussa", greeting="Hey")
</code></pre>
<p><code>log_call</code> doesn't know or care what <code>add</code> or <code>greet</code> need. It collects everything with <code>*args</code>, <code>**kwargs</code> and passed it straight through. The structure is always the same:<br /><strong>Collect</strong> --&gt; <strong>Do something</strong> --&gt; <strong>Forward</strong></p>
<hr />
<h2>Part 6: Building a Real Decorator</h2>
<p>The wrapper pattern is the foundation of Python decorators. A decorator is a function that takes a function, wraps it with extra behavior, and returns the wrapped version.</p>
<div>
<div>💡</div>
<div>You can learn more about the concept of decorators in Python on my blog. I have a <a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://blog.moussaamzat.dev/series/python-decorators" style="pointer-events:none">comprehensive series</a> that teaches you everything from the ground up.</div>
</div>

<p>Here's a <code>@timed</code> decorator that measures how long any function takes to run:</p>
<pre><code class="language-python">import time

def timed(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        elapsed_time = (time.time() - start_tim) * 1000
        print(f"Ran {func.__name__} in ~{elapsed_time:.0f}ms")
        return result
    return wrapper

@timed
def slow_add(a, b):
    time.sleep(0.1)
    return a + b

@timed
def greet(name, greeting="Hello"):
    time.sleep(0.05)
    return f"{greeting}, {name}!"

result = slow_add(1, 2)
print(result)
# Ran slow_add in ~100ms
# 3

result = greet("Moussa", greeting="Hey")
print(result)
# Ran greet in ~50ms
# Hey, Moussa!
</code></pre>
<p>Notice that <code>wrapper</code> uses <code>*args</code>, and <code>**kwargs</code> to collect and forward arguments, making <code>@timed</code> reusable across <em>any</em> function regardless of its signature. This is exactly how decorators work in Flask and FastAPI.</p>
<hr />
<h2>Key Takeaways</h2>
<ul>
<li><p><code>*args</code> collects unlimited <strong>positional</strong> arguments into a <strong>tuple</strong></p>
</li>
<li><p><code>**kwargs</code> collects unlimited <strong>keyword</strong> arguments into a <strong>dict</strong></p>
</li>
<li><p>Inside the function, both are just normal Python data structures</p>
</li>
<li><p>The correct order is always: <code>fixed -&gt; *args -&gt; **kwargs</code></p>
</li>
<li><p>Empty <code>*args</code> is <code>()</code> and empty <code>**kwargs</code> is <code>{}</code>, never <code>None</code></p>
</li>
<li><p><code>*</code> and <code>**</code> work in both directions: packing in definitions, unpacking at call sites</p>
</li>
<li><p>The wrapper pattern: <strong>collect</strong>, <strong>act</strong>, <strong>forward</strong> is the foundation of decorators.</p>
</li>
</ul>
<p>Once these rules are internalized, you'll recognize the pattern everywhere: in the standard library, in popular frameworks, and in well-written Python code across the ecosystem.</p>
<p>Thank you for reading 📖🙂.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Python Decorators From Scratch]]></title><description><![CDATA[If you've ever seen the @ symbol sitting on top of a Python function and wondered what sorcery that is, this article is for you.
Decorators are one of those Python concepts that look intimidating but ]]></description><link>https://blog.moussaamzat.dev/understanding-python-decorators-from-scratch</link><guid isPermaLink="true">https://blog.moussaamzat.dev/understanding-python-decorators-from-scratch</guid><category><![CDATA[Python]]></category><category><![CDATA[decorators in python]]></category><category><![CDATA[beginnersguide]]></category><category><![CDATA[python fundamentals]]></category><dc:creator><![CDATA[Moussa Kalam AMZAT]]></dc:creator><pubDate>Thu, 05 Mar 2026 09:30:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6992248b670db28ecd344dea/2c7e46a6-75bd-41df-ada2-f9c9ee7bc589.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you've ever seen the <code>@</code> symbol sitting on top of a Python function and wondered what sorcery that is, this article is for you.</p>
<p>Decorators are one of those Python concepts that look intimidating but are built on ideas you already know. The problem is that most tutorials jump straight to the syntax without building the mental model first. So you end up memorizing the pattern without truly understanding <strong>why</strong> it works.</p>
<p>Let's fix that. We're going to build up to decorators one concept at a time, and by the end, you'll be writing your own with confidence</p>
<hr />
<h2>Functions Are Objects: That Changes Everything</h2>
<p>Here is the thing you need to internalize: <strong>in Python, a function is just an object</strong>. Like a string, like a number, like a list, you can store it in a variable, pass it around, and do whatever you want with it.</p>
<pre><code class="language-python">def say_hello():
    print("Hello!")

# Store the function in another variable (no parentheses!)
my_function = say_hello

# Both names point to the same function
say_hello() # Hello!
my_function() # Hello!
</code></pre>
<p>Notice something critical here: <code>say_hello</code> without parentheses is the function <strong>itself</strong>, the object. <code>say_hello()</code> with parentheses <strong>calls</strong> the function and gives you the result. This distinction matters for everything that follows.</p>
<p>Think of a recipe card. <code>say_hello</code> is the card itself. You can hand it to someone, photocopy it, pin it on a wall. <code>say_hello()</code> is you actually cooking the recipe.</p>
<hr />
<h2>You Can Pass a Function to Another Function</h2>
<p>Since functions are objects, you can hand one to another function as an argument, just like you'd pass a string or a number.</p>
<pre><code class="language-python">def say_hello():
    print("Hello!")

def say_goodbye():
    print("Goodbye!")

def call_twice(func):
    func()
    func()

call_twice(say_hello)
# Hello!
# Hello!

call_twice(say_goodbye)
# Goodbye!
# Goodbye!
</code></pre>
<p><code>call_twice</code> doesn't know or care about which function you give it. It just calls whatever arrives, twice. We're passing <code>say_hello</code> (the object), not <code>say_hello()</code> (the result).</p>
<p>This might feel strange if you're coming from another language, but it's one of Python's superpowers. Functions are first-class citizens. They can go anywhere any other value can go.</p>
<hr />
<h2>A Function Can Create and Return Another Function</h2>
<p>This where things get interesting. A function can <strong>build</strong> a new function inside itself and give it back to you:</p>
<pre><code class="language-python">def make_greeter(name):
    def greeter():
        print(f"Hello, {name}!")
    return greeter

greet_moussa = make_greeter("Moussa")
greet_kalam = make_greeter("Kalam")

greet_moussa()  # Hello, Moussa!
greet_kalam()   # Hello, Kalam!
</code></pre>
<p>Each call to <code>make_greeter</code> creates a brand-new function that remembers the name it was created with. The inner function <code>greeter</code> holds onto the <code>name</code> variable even after <code>make_greeter</code> has finished executing.</p>
<p>This pattern is called <strong>closure</strong>, a function that remembers values from the scope where it was created. Closures are the engine that powers decorators, so make sure this concept clicks before moving on.</p>
<hr />
<h2>Combining the Pieces: Take a Function, Return a New One</h2>
<p>Now let's combine everything. What if we write a function that <strong>takes a function as input</strong> and <strong>returns a new function as output</strong>?</p>
<pre><code class="language-python">def make_louder(func):
    def new_function():
        print("I'M ABOUT TO DO SOMETHING!")
        func()
        print("I'M DONE!")
    return new_function

def say_hello():
    print("Hello!")

louder_hello = make_louder(say_hello)

# The original still works normally
say_hello()
# Hello!

# The new version adds extra behavior
louder_hello()
# I'M ABOUT TO DO SOMETHING!
# Hello!
# I'M DONE!
</code></pre>
<p>Please read that carefully! <code>make_louder</code> received <code>say_hello</code>, wrapped it in extra behavior (printing before and after), and returned a brand new function. The original <code>say_hello</code> is untouched.</p>
<p><strong>This is the entire idea behind decorators</strong>. Everything else is just syntax and polish.</p>
<hr />
<h2>Replacing the Original</h2>
<p>Instead of keeping both the old and new versions, what if we replace <code>say_hello</code> with the wrapped version?</p>
<pre><code class="language-python">say_hello = make_louder(say_hello)

say_hello()
# I'M ABOUT TO DO SOMETHING!
# Hello!
# I'M DONE!
</code></pre>
<p>Look at this line: <code>say_hello = make_louder(say_hello)</code>. We're saying: "take the old <code>say_hello</code>, wrap it and store the result back as <code>say_hello</code>."</p>
<p>Now whenever anyone calls <code>say_hello()</code>, they get the enhanced the version. And that one line is so common in Python that the language gives us a shortcut for it.</p>
<hr />
<h2>The <code>@</code> Symbol Is Just a Shortcut</h2>
<pre><code class="language-python">def make_louder(func):
    def new_function():
        print("I'M ABOUT TO DO SOMETHING!")
        func()
        print("I'M DONE!")
    return new_function

@make_louder
def say_hello():
    print("Hello!")

say_hello()
# I'M ABOUT TO DO SOMETHING!
# Hello!
# I'M DONE!
</code></pre>
<p><code>@make_louder</code> on top of <code>say_hello</code> is <strong>exactly identical</strong> to writing <code>say_hello = make_louder(say_hello)</code> after the function definition. It's just cleaner syntax, what Python calls "syntactic sugar".</p>
<p>That's literally all the <code>@</code> symbol does. No magic. No special mechanism. Just a shortcut for a pattern we already understand.</p>
<p><code>make_louder</code> is a <strong>decorator.</strong></p>
<hr />
<h2>The Arguments Problem</h2>
<p>There's a catch with our current approach. What if the decorated function takes arguments?</p>
<pre><code class="language-python">def log_calls(func):
    def wrapper():          # takes NO arguments!
        print(f"Calling {func.__name__}")
        func()              # passes NO arguments!
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)  # TypeError: wrapper() takes 0 arguments but 2 were given
</code></pre>
<p>It breaks because <code>add(2, 3)</code> now actually calls <code>wrapper(2, 3)</code>, but <code>wrapper</code> doesn't accept any arguments.</p>
<p>The fix is <code>*args</code> and <code>**kwargs</code>.<br />You've probably seen these before, but here's what they actually do:</p>
<ul>
<li><p><code>*args</code> lets a function accept any number of positional arguments. It packs them into a tuple.</p>
</li>
<li><p><code>**kwargs</code> does the same for keyword arguments. It packs them into a dictionary.</p>
</li>
</ul>
<p>You can read more <code>*args</code> and <code>**kwargs</code> in <a href="https://blog.moussaamzat.dev/python-args-kwargs-complete-guide">this article I wrote</a>.</p>
<p>Put them together in the wrapper, and it can accept literally <strong>any</strong> combination of arguments and pass them straight through to the original function. It doesn't need to know what the function expects. It just forwards everything.</p>
<pre><code class="language-python">def log_calls(func):
    def wrapper(*args, **kwargs):         # accept anything
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)    # forward everything
        return result                     # return the result
    return wrapper

@log_calls
def add(a, b):
    return a + b

@log_calls
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

print(add(2, 3))                          # Calling add → 5
print(greet("Moussa", greeting="Hey"))    # Calling greet → Hey, Moussa!
</code></pre>
<p>By using <code>*args</code> and <code>**kwargs</code>, the wrapper doesn't need to know anything about the original function's signature. It catches everything and forwards it blindly. This is what makes the decorator generic, working with <strong>any</strong> function.</p>
<hr />
<h2>A Practical Example: Logging</h2>
<p>Let's see why this pattern is actually useful. Imagine you want to log every time certain functions are called:</p>
<pre><code class="language-python">def log_calls(func):
    def wrapper(*args, **kwargs):
        print(f"&gt;&gt;&gt; Calling {func.__name__}()")
        result = func(*args, **kwargs)
        print(f"&gt;&gt;&gt; {func.__name__}() finished")
        return result
    return wrapper

@log_calls
def process_payment():
    print("Payment processed!")

@log_calls
def send_email():
    print("Email sent!")

@log_calls
def update_database():
    print("Database updated!")
</code></pre>
<div>
<div>💡</div>
<div>Call each of the decorated functions, and analyze the results.</div>
</div>

<p>You wrote the logging logic <strong>once</strong> and applied it to three functions. Without decorators, you'd have to copy and paste those <code>print</code> statement into every single function. And when you want to change the log format, you change it in one place instead of hunting through your entire codebase.</p>
<hr />
<h2>The Universal Template</h2>
<p>Every decorator follows the same recipe:</p>
<pre><code class="language-python">def my_decorator(func):            # 1. Take a function in
    def wrapper(*args, **kwargs):   # 2. Create a new function
        # do something before
        result = func(*args, **kwargs)  # 3. Call the original
        # do something after
        return result               # 4. Return the result
    return wrapper                  # 5. Return the wrapper

@my_decorator                      # 6. Replace the original
def my_function():
    pass
</code></pre>
<div>
<div>💡</div>
<div>That's the pattern. Memorize it now that you understand the idea behind it. Once this clicks, you can write any decorator by just filling in the <strong>before </strong>and <strong>after</strong> parts.</div>
</div>

<hr />
<h2>One Last Best Practice: <code>functools.wraps</code></h2>
<p>When you wrap a function, the wrapper replaces the original's metadata: its name, docstring, and other attributes. This can cause confusion during debugging.</p>
<pre><code class="language-python">@log_calls
def add(a, b):
    """Add two numbers.""" # Here is the docstring
    return a + b

print(add.__name__)  # "wrapper" — not "add"!
print(add.__doc__)  # None — the docstring is gone! 
</code></pre>
<p>Fix this with <code>functools.wraps</code></p>
<pre><code class="language-python">from functools import wraps


def log_calls(func):
    @wraps(func)  # preserves func's name, docstring, etc.
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        return result
    return wrapper


@log_calls
def add(a, b):
    """Add two numbers.""" # Here is the docstring
    return a + b

print(add.__name__)  # add
print(add.__doc__)  # Add two numbers.
</code></pre>
<div>
<div>💡</div>
<div>I'd advise you always use <code>@wraps</code>. It's a one-line addition that prevents real headaches down the road.</div>
</div>

<hr />
<h2>What's Next?</h2>
<p>You now understand the complete foundation of Python decorators. In the next article, we'll tackle a subtle but critical trap that catches almost every developer learning decorators: the difference between <code>print</code> and <code>return</code> inside wrappers, and why confusing them makes your values silently disappear.</p>
<hr />
<blockquote>
<p><em>This is Part 1 of my Python Decorators series. Follow along for the next parts where we cover the</em> <code>print</code> <em>vs</em> <code>return</code> <em>trap, decorators with arguments, stacking, and practice exercises.</em></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Welcome to Build, Break, Learn]]></title><description><![CDATA[If you're reading this, you're probably a developer, or becoming one.Either way, welcome. I'm glad you're here 🙂.
I'm Moussa Kalam AMZAT, a proud citizen of the Benin Republic 🇧🇯.I'm a Software Eng]]></description><link>https://blog.moussaamzat.dev/welcome</link><guid isPermaLink="true">https://blog.moussaamzat.dev/welcome</guid><category><![CDATA[introduction]]></category><category><![CDATA[beginnersguide]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[AI]]></category><category><![CDATA[Blogging]]></category><dc:creator><![CDATA[Moussa Kalam AMZAT]]></dc:creator><pubDate>Tue, 03 Mar 2026 22:19:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6992248b670db28ecd344dea/d61d8013-a6fe-4975-b439-91b812a5bbc3.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you're reading this, you're probably a developer, or becoming one.<br />Either way, welcome. I'm glad you're here 🙂.</p>
<p>I'm <strong>Moussa Kalam AMZAT</strong>, a proud citizen of the Benin Republic 🇧🇯.<br />I'm a Software Engineer with 2+ years of work experience based in Kigali, Rwanda, and this blog is something I should have started a long time ago.<br />Let me tell you why.</p>
<h2>The long road here</h2>
<p>My journey into tech didn't start with a computer science degree. It started with Diplomacy and International Relations. That's what I studied. That's the career I was building. And for a while, that was the plan.</p>
<p>But somewhere along the way, I feel in love with learning autonomously and building things. Not policy frameworks, actual systems that solve real problems. So I made a decision that scared me: I broke away from everything I knew and started over in tech. From ZERO.</p>
<p>So I went all in. I enrolled in a BSc. (Honors) in Software Engineering, graduated in June 2025, and taught myself everything I could alongside it: YouTube tutorials, documentation, online courses, bootcamps, and a stubborn refusal to give up.</p>
<p>That journey, building something, breaking away from it and learning through the process, is exactly why this blog is called <strong>Build, Break, Learn.</strong> It's not just catchy name. It's the cycle that defines how I got here, and it's how I approach every new thing I learn as a developer: I build it, I break it, I figure out why, and I come out the other side understanding it at a level I couldn't have reached by just reading about it.</p>
<h2>Why this blog exists</h2>
<p>For years, every time I learned something new, I documented it. Detailed notes, step-by-step breakdowns, real examples, the works. But I never shared any of it. I figured everything was already on the internet. Someone smarter had already explained it better. Why bother?</p>
<p>Then a colleague of mine, <strong>Fabien Ishimwe</strong>, asked me a question I couldn't answer: "You document everything you learned. You are great at explaining what you know. Why are you keeping all that to yourself?"</p>
<p>He was right. So here I am.</p>
<h2>Who is this blog for</h2>
<p>I write for developers who want to actually understand what they're using, not just copy-paste code and hope it works.</p>
<p>If you've ever stared at a tutorial and thought "Okay, but <strong>why</strong> does this work?", this blog is for you.</p>
<p>If you're starting out and most resources feel like they're written for people who already know the thing they're trying to learn, this blog is for you.</p>
<p>I especially write with aspiring developers and students in mind, because I believe we deserve technical content that is practical, clear, and doesn't assume we all had the same starting point.</p>
<h2>How I write</h2>
<p>I have a few principles that shape every article on this blog:</p>
<ul>
<li><p><strong>I put myself in your shoes</strong>: I mainly write about things that took me time to understand. If something confused me, I assume it'll confuse someone else too. So I take the time to break it down the way I wish someone had broken it down for me.</p>
</li>
<li><p><strong>I explain the why, not just the how</strong>: I'm not interested in showing you how to use something without helping you understand why it works. I want you to see what's happening underneath, how the pieces connect, and what's actually going on when you run that code.</p>
</li>
<li><p><strong>I keep it digestible</strong>: No 45-min reads. I organize topics into short series so you can learn at your own pace without feeling overwhelmed. Each article is a small, focused step forward.</p>
</li>
<li><p><strong>I write like we're talking</strong>: No academic tone. No unnecessary jargon. Just a clear, honest conversation between two people who care about getting better at this craft.</p>
</li>
</ul>
<h2>What I'll be covering</h2>
<p>This blog will span everything I'm learning and building as a developer:</p>
<ul>
<li><p><strong>Full-stack web development</strong>: <code>TypeScript</code>, <code>React</code>, <code>Next.js</code>, and the frameworks and tools I use every day.</p>
</li>
<li><p><strong>Python</strong>: From the fundamentals to more advanced patterns.</p>
</li>
<li><p><strong>AI and Data</strong>: I'm investing deeply into this space and I'll be documenting as I go.</p>
</li>
<li><p><strong>Authentication and databases</strong>: Real-world patterns using the best and recommended tools out there.</p>
</li>
<li><p><strong>Developer Life</strong>: Career switches, learning strategies, and the things nobody tells you when you're starting out.</p>
</li>
</ul>
<div>
<div>💡</div>
<div>New articles will be dropping 1 - 2 times a week, organized into series so you can follow along at your own pace.</div>
</div>

<h2>A personal note</h2>
<p>If you're someone who's just getting started in tech, or if you've ever felt like you don't have the <strong>"right"</strong> background to be a developer, I want you to know that I get it. I've been there. I studied diplomacy and international relations. I learned English on my own through the internet and English communities in Benin, a country that primarily speaks French, and then switched careers in a way that baffled many people I knew.</p>
<p>And here I am, writing to you as a Software Engineer.</p>
<p>The path doesn't have to be straight. It just has to keep moving. Some moves are so decisive that they don't need constant motion to matter. Build, break, learn and repeat.</p>
<p>I'm glad you're here. Let's learn together</p>
<p>-- <a href="https://linkedin.com/in/moussakalamamzat">Moussa Kalam AMZAT</a></p>
<blockquote>
<p>Add value and get rewarded ❤️</p>
</blockquote>
]]></content:encoded></item></channel></rss>