π Decorators
A decorator is a function that adds or modifies the behavior of another function without changing its original code.
It uses the @ symbol.
πΉ Basic Example
def decorator(func):
def wrapper():
print("Before function")
func()
print("After function")
return wrapper
@decorator
def greet():
print("Hello!")
greet()Before function
Hello!
After functionπ How It Works
This:
@decorator
def greet():
print("Hello!")is equivalent to:
def greet():
print("Hello!")
greet = decorator(greet)So Python passes greet to the decorator.
π§© Decorator with Arguments
If the original function accepts arguments, the wrapper can use *args and **kwargs.
def decorator(func):
def wrapper(*args, **kwargs):
print("Function called")
return func(*args, **kwargs)
return wrapper
@decorator
def add(a, b):
return a + b
print(add(10, 5))Function called
15β Common Uses
Decorators are commonly used for:
π Authentication
β±οΈ Timing functions
π Logging
β Validation
π Access control
Decorators rely on two concepts you've already learned: first-class functions and closures.
<div class="infographic-pydecorator">
<style>
.infographic-pydecorator {
--ink:#1a1a1a; --ink-soft:#5c5c5c; --paper:#fff; --paper-alt:#f0ece4;
--accent:#2563eb; --accent-soft:#e3ebfd; --line:#ddd6c9;
--c-before:#2563eb; --c-before-soft:#e3ebfd;
--c-call:#1f9d55; --c-call-soft:#e3f5ec;
--c-after:#9333ea; --c-after-soft:#f3e8fd;
--mono-font:'IBM Plex Mono','SFMono-Regular',Menlo,monospace;
--display-font:'Fraunces',Georgia,serif; --body-font:'Inter',system-ui,sans-serif;
box-sizing:border-box; max-width:700px; margin:0 auto; font-family:var(--body-font); color:var(--ink); background:var(--paper);
}
.infographic-pydecorator[data-theme="dark"] {
--ink:#e8e6e1; --ink-soft:#9a968c; --paper:#121212; --paper-alt:#1e1e26;
--accent:#5b8def; --accent-soft:#202942; --line:#2c313d;
--c-before:#5b8def; --c-before-soft:#202942;
--c-call:#6fdc8c; --c-call-soft:#16342a;
--c-after:#c58af2; --c-after-soft:#2e2140;
}
.infographic-pydecorator *,.infographic-pydecorator *::before,.infographic-pydecorator *::after{box-sizing:border-box;}
.infographic-pydecorator .dc-wrap{padding:0 clamp(1rem,4vw,2rem) 1.5rem;}
.infographic-pydecorator h1{font-family:var(--display-font);font-weight:600;font-size:clamp(1.4rem,5vw,1.9rem);text-align:center;margin:1.5rem 0 0.25rem;letter-spacing:-0.01em;}
.infographic-pydecorator .dc-sub{text-align:center;color:var(--ink-soft);font-size:0.85rem;margin:0 0 1.3rem;}
.infographic-pydecorator .dc-tabs{display:flex;justify-content:center;gap:0.35rem;margin-bottom:1.4rem;border-bottom:1px solid var(--line);flex-wrap:wrap;}
.infographic-pydecorator .dc-tab-btn{font:inherit;font-family:var(--mono-font);font-size:0.8rem;font-weight:700;background:none;border:none;border-bottom:2.5px solid transparent;color:var(--ink-soft);padding:0.55rem 0.85rem;cursor:pointer;margin-bottom:-1px;}
.infographic-pydecorator .dc-tab-btn.is-active{color:var(--accent);border-bottom-color:var(--accent);}
.infographic-pydecorator .dc-panel.is-hidden-tab{display:none;}
.infographic-pydecorator .code-block{font-family:var(--mono-font);font-size:0.76rem;line-height:1.7;white-space:pre;background:var(--paper-alt);border:1px solid var(--line);border-radius:8px;padding:0.8rem 0.95rem;margin-bottom:1rem;}
.infographic-pydecorator .code-block .kw{color:var(--accent);font-weight:700;}
.infographic-pydecorator .code-block .deco{font-weight:700; color:var(--c-call);}
.infographic-pydecorator .blk{border-radius:5px;padding:0.05rem 0.15rem;transition:background .3s,box-shadow .3s;}
.infographic-pydecorator .blk.before-b.is-on{background:var(--c-before-soft);box-shadow:0 0 0 1.5px var(--c-before);}
.infographic-pydecorator .blk.call-b.is-on{background:var(--c-call-soft);box-shadow:0 0 0 1.5px var(--c-call);}
.infographic-pydecorator .blk.after-b.is-on{background:var(--c-after-soft);box-shadow:0 0 0 1.5px var(--c-after);}
.infographic-pydecorator .equiv-note{text-align:center;font-family:var(--mono-font);font-size:0.75rem;color:var(--ink-soft);margin-bottom:1rem;}
.infographic-pydecorator .equiv-note strong{color:var(--ink);}
.infographic-pydecorator .row{display:flex;justify-content:center;gap:0.5rem;margin-bottom:0.9rem;flex-wrap:wrap;align-items:center;}
.infographic-pydecorator .row input{font-family:var(--mono-font);font-size:0.85rem;padding:0.4rem 0.6rem;border:1.5px solid var(--line);border-radius:5px;background:var(--paper-alt);color:var(--ink);width:110px;text-align:center;}
.infographic-pydecorator .row input:focus{outline:none;border-color:var(--accent);}
.infographic-pydecorator .btn{font:inherit;font-family:var(--mono-font);font-size:0.85rem;font-weight:700;background:var(--accent);color:#fff;border:none;border-radius:999px;padding:0.55rem 1.2rem;cursor:pointer;}
.infographic-pydecorator .btn:disabled{opacity:0.5;cursor:default;}
.infographic-pydecorator .device{--ed-bg:#1e1e1e;--ed-bar:#2d2d2d;--ed-text:#d4d4d4;--ed-soft:#8a8a8a;--ed-ok:#6fdc8c;background:var(--ed-bg);border:1px solid var(--line);border-radius:8px;overflow:hidden;}
.infographic-pydecorator .device-bar{display:flex;align-items:center;gap:0.4rem;background:var(--ed-bar);padding:0.5rem 0.7rem;border-bottom:1px solid #3a3a3a;font-size:0.72rem;color:var(--ed-soft);font-weight:600;}
.infographic-pydecorator .device-bar .dot{width:9px;height:9px;border-radius:50%;background:#5a5a5a;}
.infographic-pydecorator .device-bar .dot:nth-child(1){background:#ff5f56;}
.infographic-pydecorator .device-body{padding:0.75rem 0.9rem;font-family:var(--mono-font);font-size:0.8rem;color:var(--ed-text);min-height:90px;}
.infographic-pydecorator .log-line{line-height:1.7;opacity:0;animation:dc-fadein .25s ease forwards;}
@keyframes dc-fadein{from{opacity:0;}to{opacity:1;}}
.infographic-pydecorator .log-empty{color:var(--ed-soft);}
.infographic-pydecorator .count-badge{text-align:center;font-family:var(--mono-font);font-size:0.85rem;color:var(--accent);font-weight:700;margin-top:0.8rem;}
.infographic-pydecorator .dc-caption{text-align:center;font-size:0.85rem;color:var(--ink-soft);margin-top:0.9rem;}
@media (prefers-reduced-motion:reduce){.infographic-pydecorator *,.infographic-pydecorator *::before,.infographic-pydecorator *::after{animation-duration:.001ms!important;transition-duration:.001ms!important;}}
</style>
<div class="dc-wrap">
<h1>Decorators</h1>
<p class="dc-sub">A function that wraps another function β adding behavior before and after, without touching its code.</p>
<div class="dc-tabs">
<button class="dc-tab-btn is-active" data-tab="wrap">Before & After</button>
<button class="dc-tab-btn" data-tab="count">Counting Calls</button>
</div>
<!-- WRAP TAB -->
<div class="dc-panel" id="tabWrap">
<div class="code-block"><span class="kw">def</span> my_decorator(func):
<span class="kw">def</span> wrapper(*args, **kwargs):
<span class="blk before-b" id="bBefore">print("Before the function runs")</span>
result = <span class="blk call-b" id="bCall">func(*args, **kwargs)</span>
<span class="blk after-b" id="bAfter">print("After the function runs")</span>
<span class="kw">return</span> result
<span class="kw">return</span> wrapper
<span class="deco">@my_decorator</span>
<span class="kw">def</span> greet(name):
print(f"Hello, {'{name}'}!")</div>
<p class="equiv-note">@my_decorator above greet means exactly: <strong>greet = my_decorator(greet)</strong></p>
<div class="row">
<input id="nameInput" type="text" value="Alice" />
<button class="btn" id="wrapRunBtn">Run: greet("Alice")</button>
</div>
<div class="device">
<div class="device-bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span> Terminal</div>
<div class="device-body" id="wrapLog"><div class="log-empty">Hit Run β watch which part of wrapper() executes at each step.</div></div>
</div>
<p class="dc-caption">greet("Alice") doesn't call the original function directly anymore β it calls wrapper(), which decides when the original runs.</p>
</div>
<!-- COUNT TAB -->
<div class="dc-panel is-hidden-tab" id="tabCount">
<div class="code-block"><span class="kw">def</span> count_calls(func):
count = 0
<span class="kw">def</span> wrapper(*args, **kwargs):
<span class="kw">nonlocal</span> count
count += 1
print(f"Call #{'{count}'} to {'{func.__name__}'}")
<span class="kw">return</span> func(*args, **kwargs)
<span class="kw">return</span> wrapper
<span class="deco">@count_calls</span>
<span class="kw">def</span> greet(name):
print(f"Hello, {'{name}'}!")</div>
<div class="row">
<input id="countNameInput" type="text" value="Bob" />
<button class="btn" id="countRunBtn">Run: greet("Bob")</button>
</div>
<div class="device">
<div class="device-bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span> Terminal</div>
<div class="device-body" id="countLog"><div class="log-empty">Call greet() a few times and watch the counter climb.</div></div>
</div>
<p class="count-badge" id="countBadge">count = 0</p>
<p class="dc-caption">The decorator's own closure variable (count) survives between calls β same trick generators and closures both use.</p>
</div>
</div>
<script>
(function(){
var root = document.currentScript.closest('.infographic-pydecorator') || document.querySelector('.infographic-pydecorator');
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark = bodyAttr === 'true' ? true : bodyAttr === 'false' ? false : (document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/) || [])[1] === 'true';
if (dark) root.setAttribute('data-theme', 'dark');
var tabBtns = root.querySelectorAll('.dc-tab-btn');
var panels = { wrap: root.querySelector('#tabWrap'), count: root.querySelector('#tabCount') };
tabBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
tabBtns.forEach(function (b) { b.classList.toggle('is-active', b === btn); });
var t = btn.dataset.tab;
Object.keys(panels).forEach(function (k) { panels[k].classList.toggle('is-hidden-tab', k !== t); });
});
});
/* ---------------- WRAP TAB ---------------- */
var nameInput = root.querySelector('#nameInput');
var wrapRunBtn = root.querySelector('#wrapRunBtn');
var wrapLog = root.querySelector('#wrapLog');
var bBefore = root.querySelector('#bBefore');
var bCall = root.querySelector('#bCall');
var bAfter = root.querySelector('#bAfter');
var wrapBlocks = [bBefore, bCall, bAfter];
var wrapRunning = false;
function clearWrapHl() { wrapBlocks.forEach(function (b) { b.classList.remove('is-on'); }); }
wrapRunBtn.addEventListener('click', function () {
if (wrapRunning) return;
wrapRunning = true;
wrapRunBtn.disabled = true;
clearWrapHl();
wrapLog.innerHTML = '';
var name = nameInput.value.trim() || 'Alice';
var steps = [
function () { bBefore.classList.add('is-on'); addLog('Before the function runs'); },
function () { bBefore.classList.remove('is-on'); bCall.classList.add('is-on'); addLog('Hello, ' + name + '!'); },
function () { bCall.classList.remove('is-on'); bAfter.classList.add('is-on'); addLog('After the function runs'); },
function () { bAfter.classList.remove('is-on'); wrapRunning = false; wrapRunBtn.disabled = false; }
];
function addLog(text) {
var line = document.createElement('div');
line.className = 'log-line';
line.textContent = text;
wrapLog.appendChild(line);
}
var i = 0;
function next() {
if (i < steps.length) { steps[i](); i++; setTimeout(next, 650); }
}
next();
});
/* ---------------- COUNT TAB ---------------- */
var countNameInput = root.querySelector('#countNameInput');
var countRunBtn = root.querySelector('#countRunBtn');
var countLog = root.querySelector('#countLog');
var countBadge = root.querySelector('#countBadge');
var count = 0;
var firstCountLog = true;
countRunBtn.addEventListener('click', function () {
var name = countNameInput.value.trim() || 'Bob';
count++;
countBadge.textContent = 'count = ' + count;
if (firstCountLog) { countLog.innerHTML = ''; firstCountLog = false; }
var l1 = document.createElement('div');
l1.className = 'log-line';
l1.textContent = 'Call #' + count + ' to greet';
countLog.appendChild(l1);
var l2 = document.createElement('div');
l2.className = 'log-line';
l2.textContent = 'Hello, ' + name + '!';
countLog.appendChild(l2);
countLog.scrollTop = countLog.scrollHeight;
});
})();
</script>
</div>