100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
HomeBlogPython Decorators: A Practical Guide for Beginners
Programming

Python Decorators: A Practical Guide for Beginners

SV

SkillVeris Team

Engineering Team

May 27, 2026 10 min read
Share:
Python Decorators: A Practical Guide for Beginners
Key Takeaway

A decorator is a function that takes another function and returns a new function with added behaviour.

In this guide, you'll learn:

  • The @decorator syntax is just shorthand for fn = decorator(fn).
  • Always use @functools.wraps(func) inside your wrapper to preserve the original function's name, docstring, and metadata.
  • Decorators are how logging, caching, authentication, and retry logic are added cleanly in Python.
  • Decorator factories add a third nesting level so you can pass arguments to a decorator.

1What Is a Decorator?

A decorator is a function that wraps another function, adding behaviour before and/or after the original runs — without modifying the original function's code. Classic use cases include logging, timing, caching, authentication, retry logic, and input validation.

The @decorator syntax is just shorthand. Applying a decorator with @ is exactly the same as reassigning the function to the decorator's return value.

The two forms are identical

These produce the same result:

code
# With @ syntax
@my_decorator
def my_function():
    pass

# Without @ syntax (what Python actually does)
def my_function():
    pass
my_function = my_decorator(my_function)

2Functions Are First-Class Objects

Decorators are possible because Python functions are first-class objects: they can be assigned to variables, passed as arguments, and returned from other functions. Once you can return a function from another function, you can build a decorator.

Functions as values

Assign, pass, and return functions:

code
def greet(name: str) -> str:
    return f"Hello, {name}!"

# Assign to variable
say_hello = greet
print(say_hello("Sathya"))  # Hello, Sathya!

# Pass as argument
def apply(fn, value):
    return fn(value)
print(apply(greet, "World"))  # Hello, World!

# Return from function
def make_multiplier(n):
    def multiply(x):
        return x * n
    return multiply  # returns the function itself, not its result

triple = make_multiplier(3)
print(triple(10))  # 30

3Building Your First Decorator

A timing decorator measures how long the wrapped function takes to run. The outer function receives func, the inner wrapper adds logic around the call, and the outer function returns the wrapper.

The pattern is always the same: outer function receives func, inner wrapper adds logic around the call to func, @functools.wraps(func) preserves metadata, and the outer function returns wrapper.

The four parts of every decorator: outer function, wrapper, functools.wraps, and return wrapper.
The four parts of every decorator: outer function, wrapper, functools.wraps, and return wrapper.

A timer decorator

Measure and print execution time:

code
import functools

def timer(func):
    # Measures and prints how long func takes to run.
    @functools.wraps(func)  # <- critical: preserves metadata
    def wrapper(*args, **kwargs):
        import time
        start = time.perf_counter()
        result = func(*args, **kwargs)  # <- call the original function
        end = time.perf_counter()
        print(f"{func.__name__} took {(end-start)*1000:.2f}ms")
        return result
    return wrapper

@timer
def slow_sum(n: int) -> int:
    return sum(range(n))

result = slow_sum(10_000_000)
# slow_sum took 243.12ms

4The @ Syntax

The @ decorator syntax was introduced in Python 2.4 specifically to make this pattern cleaner. Decorating a function with @timer is exactly equivalent to reassigning it to timer(my_fn).

Multiple decorators stack: @A @B def f() is equivalent to f = A(B(f)) — the inner decorator applies first.

Equivalent forms

The @ form unpacked:

code
# These are exactly equivalent:
@timer
def my_fn():
    pass

def my_fn():
    pass
my_fn = timer(my_fn)

5functools.wraps: Why It Matters

Without @functools.wraps, every decorated function inherits the wrapper's name and loses its docstring, which breaks debugging, logging, and documentation tools. Adding @functools.wraps(func) copies the original function's metadata onto the wrapper.

The fix is one line, so always include it inside your wrapper definitions.

💡Remember

Without @functools.wraps, every decorated function has __name__ = "wrapper", breaking debugging, logging, and documentation tools. Always include it.

Without and with wraps

Compare the metadata that survives:

code
# WITHOUT @functools.wraps
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def my_fn():
    # My docstring
    pass

print(my_fn.__name__)  # wrapper <- wrong!
print(my_fn.__doc__)   # None <- docstring lost!

# WITH @functools.wraps
def good_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@good_decorator
def my_fn():
    # My docstring
    pass

print(my_fn.__name__)  # my_fn <- correct
print(my_fn.__doc__)   # My docstring <- preserved

6Decorators with Arguments

To pass arguments to a decorator, add another layer of nesting — a "decorator factory" that returns a decorator configured with those arguments. The outermost function takes the configuration, the middle returns the decorator, and the inner wrapper does the work.

The retry example below accepts a maximum number of attempts and a delay, then retries the wrapped function on failure.

A retry decorator factory

Three levels of nesting for configurable behaviour:

code
def retry(max_attempts: int = 3, delay: float = 1.0):
    # Decorator factory: returns a decorator configured with arguments.
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            import time
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        raise
                    print(f"Attempt {attempt} failed: {e}. Retrying in {delay}s...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=5, delay=0.5)
def flaky_api_call():
    import random
    if random.random() < 0.7:
        raise ConnectionError("Server unavailable")
    return "Success"

7Real-World Decorators

The same pattern powers a handful of decorators you'll reach for constantly: logging calls, enforcing authentication, and caching results. Each is a small wrapper around the original function.

The four most common real-world decorator patterns: logging, timing, caching, and auth checking.
The four most common real-world decorator patterns: logging, timing, caching, and auth checking.

Logging, auth, and cache

Three practical decorators:

code
# 1. Logging decorator
def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}({args}, {kwargs})")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result!r}")
        return result
    return wrapper

# 2. Authentication decorator (FastAPI-style)
def require_auth(func):
    @functools.wraps(func)
    def wrapper(request, *args, **kwargs):
        if not request.headers.get("Authorization"):
            raise PermissionError("Authentication required")
        return func(request, *args, **kwargs)
    return wrapper

# 3. Simple in-memory cache
def cache(func):
    stored = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args not in stored:
            stored[args] = func(*args)
        return stored[args]
    return wrapper

8Stacking Decorators

Decorators stack from bottom to top. With @timer over @log_calls over @retry, retry wraps the function first, then log_calls wraps that, then timer wraps the outermost.

When the function is called, execution flows from the outermost decorator inward: timer → log_calls → retry → the actual function.

Stacked decorators

Three decorators on one function:

code
@timer
@log_calls
@retry(max_attempts=3)
def fetch_data(url: str) -> dict:
    import httpx
    return httpx.get(url).json()

9Class-Based Decorators

When a decorator needs to maintain state, a class with a __call__ method is cleaner than a nested closure. The __init__ stores the wrapped function and initial state, and __call__ runs on every invocation.

The CountCalls example below tracks how many times the decorated function has been called and exposes the count as an attribute.

A stateful decorator class

Track call counts with __call__:

code
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
        print(f"{self.func.__name__} called {self.count} times")
        return self.func(*args, **kwargs)

@CountCalls
def process():
    return "done"

process()  # process called 1 times
process()  # process called 2 times
print(process.count)  # 2

10Built-In Decorators

Python and its standard library ship many decorators you'll use directly. The table below pairs each with what it does and where it lives.

  • @functools.lru_cache(maxsize=128) — memoises function results · functools
  • @functools.cache — unbounded lru_cache (Python 3.9+) · functools
  • @staticmethod — no self/cls; a utility function in a class · built-in
  • @classmethod — receives cls; alternative constructors · built-in
  • @property — attribute-style method access · built-in
  • @dataclasses.dataclass — generates __init__, __repr__, __eq__ · dataclasses
  • @app.route / @app.get — register URL routes · Flask/FastAPI

11When NOT to Use Decorators

Decorators add reuse and clarity, but they can be the wrong tool. Avoid them when there's no reuse benefit, when they change the function's contract, or when stacking gets too deep.

  • One-off logic — if you'll only wrap one function once, a decorator adds complexity for no reuse benefit; just add the logic inline.
  • Changing the contract — decorators should preserve the wrapped function's signature and return type; changing them breaks static analysis and documentation.
  • Deep stacking — more than three or four decorators on one function becomes hard to reason about; consider refactoring instead.

12Key Takeaways

Decorators are a compact, reusable way to add cross-cutting behaviour, and a few habits keep them transparent and maintainable.

  • A decorator is a function that takes a function and returns a new function with added behaviour.
  • Always use @functools.wraps(func) inside your wrapper to preserve __name__, __doc__, and other metadata.
  • Decorator factories (decorators with arguments) add a third nesting level.
  • Class-based decorators are cleaner when the decorator needs to maintain state.
  • Use @functools.lru_cache for free memoisation of pure functions.

13What to Learn Next

Decorators show up everywhere in idiomatic Python, so these topics build naturally on them.

  • Python OOP — decorators are used extensively with classes.
  • Async Python — async decorators follow the same pattern with an async def wrapper.
  • FastAPI REST API Project — routes, dependencies, and middleware use decorators extensively.

14Frequently Asked Questions

Can I decorate a class instead of a function? Yes. Class decorators receive the class object and return a modified class, with @dataclass being the most famous example. You can also decorate class methods with @staticmethod, @classmethod, and @property.

How do I write an async decorator? Use async def wrapper(*args, **kwargs) instead of def wrapper, and return await func(*args, **kwargs). Everything else is identical, and the outer decorator function itself doesn't need to be async.

What is the difference between a decorator and a context manager? A decorator wraps a function call, adding logic before and after every invocation. A context manager (the with statement) wraps a block of code, managing resources for that block. Use decorators for reusable function-level behaviour and context managers for resource management within a function.

Why does Python use @functools.wraps instead of something simpler? Without it, every decorated function appears to have the name, docstring, and signature of the inner wrapper, which breaks help(), logging, debugging tools, and type checkers. @functools.wraps copies the relevant attributes from the original to the wrapper, making the decorator transparent to introspection tools.

📄

Get The Print Version

Download a PDF of this article for offline reading.

About the Publisher

SV

SkillVeris Team

Engineering Team

Our engineering writers turn abstract code concepts into hands-on, project-driven learning experiences.

View all posts

Never miss an update

Get the latest tutorials and guides delivered to your inbox.

No spam. Unsubscribe anytime.

Frequently Asked Questions

21 categories · pick one to explore

Does SkillVeris have a tech blog, and what does it cover?
Yes, the SkillVeris blog has over 500 articles covering AI and machine learning, programming, web development, DevOps, cloud, security, databases and career guidance. Articles are practical and answer-first, and many use the Learn Through Hobbies approach, teaching technical concepts through cricket, music, gaming or cooking analogies. Everything is free to read.
What is the SkillVeris tech glossary and how big is it?
The SkillVeris glossary is a free reference of roughly 2,000-plus technology terms, each with a clear plain-language definition. It spans AI, programming, web, DevOps, cloud, security and database vocabulary, so whenever a lesson, article or job description uses jargon you do not recognise, the glossary gives you a fast, reliable answer.
Are the developer cheat sheets on SkillVeris free to download?
The cheat sheets are completely free to use, like everything else on SkillVeris. Each sheet condenses a language or tool into its essential syntax, commands and patterns for quick reference while coding. They are designed for rapid lookup during real work, complementing the deeper explanations found in study notes and courses.
Which programming references and cheat sheets are available?
Cheat sheets cover the platform's main domains, including programming languages, AI and ML tooling, web development, DevOps, cloud, security and databases, matching the topics of the 37 live courses. Each sheet lists related reading links and hashtags, so you can jump from a quick reference into fuller study notes or blog articles.
How do I find the meaning of a technical term quickly?
Search the SkillVeris glossary, which holds around 2,000-plus terms with concise, plain-language definitions. Each entry gets to the point in its first sentence, then links to related reading like blog posts or study notes for deeper context. It is faster and more consistent than sifting through scattered search results.
Is the SkillVeris blog good for beginners learning to code?
Yes, many blog articles are written specifically for beginners, and the Learn Through Hobbies style makes them unusually approachable: you might learn Python concepts through cricket or understand APIs through cooking. With 500-plus articles across skill levels, beginners can start with fundamentals and keep reading as they advance, entirely free.
Can cheat sheets replace full courses for learning a language?
No, cheat sheets are references, not teaching tools; they assume you already understand the concepts and just need syntax or commands fast. To actually learn a language, take a structured SkillVeris course with its 24–40 lessons and assessments, then keep the cheat sheet beside you while practising in Code Lab.
How often are new blog articles published on SkillVeris?
The blog grows regularly and already exceeds 500 articles, with new posts added as courses launch and technologies evolve. Topics track the platform's catalogue across AI, programming, web development, DevOps, cloud and security, so checking the Blog section periodically surfaces fresh tutorials, explainers and career-focused pieces, all free to read.
Does the glossary cover AI and machine learning terms?
Yes, AI and machine learning vocabulary is a major part of the roughly 2,000-plus term glossary, covering everything from foundational terms to modern concepts around LLMs, RAG and MLOps. Definitions are plain-language and answer-first, which helps when dense AI papers or course lessons throw unfamiliar jargon at you.
Are there cheat sheets for interview preparation?
Cheat sheets work well as interview-day refreshers because they compress syntax, commands and key concepts into scannable references. For dedicated preparation, combine them with the SkillVeris interview questions feature, which includes readiness scoring, plus study notes for depth. Reviewing a relevant cheat sheet just before an interview steadies recall under pressure.
Can I read the tech blog without signing up?
Yes, the blog is freely readable, and SkillVeris never charges for content. All 500-plus articles are open, covering tutorials, concept explainers and career advice. Creating a free account adds value elsewhere on the platform, like course progress tracking and certificates, but reading the blog requires no commitment at all.
How is the SkillVeris glossary different from Wikipedia?
The glossary is purpose-built for learners: definitions are short, plain-language and answer-first, sized for a quick lookup mid-lesson rather than a deep encyclopedic read. Entries also cross-link to related SkillVeris study notes, blog posts and courses, so a definition becomes a doorway into structured learning instead of a dead end.
Do blog articles use the Learn Through Hobbies method?
Many blog articles teach technical topics through hobby analogies, a hallmark of the SkillVeris blog, so you will find articles explaining programming through cricket, machine learning through music, or system design through cooking. The analogy is the teaching device; the article still delivers the real technical concept underneath.
Where can I find quick programming references while coding?
Open the SkillVeris cheat sheets, which are built exactly for that moment: compact, scannable references for syntax, commands and common patterns across languages and tools. Keep the relevant sheet in a browser tab while you work in Code Lab or your own editor, and dip into the glossary for terminology.
Is there a glossary entry for terms I meet in job descriptions?
Very likely yes, with roughly 2,000-plus terms across AI, programming, web, DevOps, cloud, security and databases, the glossary covers most jargon that appears in tech job descriptions. Decoding a listing this way helps you judge role fit honestly and prepares you to discuss those terms in interviews.
Are the blog articles written for the Indian tech audience?
The blog serves Indian learners plus a worldwide audience. Content stays globally relevant while acknowledging realities that matter in India, such as free access being essential for students and freshers, and career guidance that connects naturally to the SkillVeris jobs portal, which aggregates roles across India, UK, USA, Germany and Remote.
Can I suggest a topic for the blog or glossary?
SkillVeris content grows in response to what learners need, so feedback is welcome through the platform's support channels. If a term is missing from the glossary or a topic deserves an article, telling the team helps prioritise it. Meanwhile, the AI Mentor can answer the question immediately, 24/7, at any depth.
Do cheat sheets and glossary entries link to deeper learning?
Yes, every cheat sheet and glossary entry carries related reading links into study notes, blog articles and courses, plus concept hashtags for discovering similar content. This cross-linking means a thirty-second lookup can smoothly become a structured learning session whenever you decide you want more than a quick answer.
What makes SkillVeris programming references trustworthy?
The references are written to strict internal quality standards, kept consistent with the platform's 37 live courses, and never padded with invented statistics or hype. Definitions and cheat sheets are reviewed against the same content contracts that govern courses, and the answer-first style makes any inaccuracy easy to spot and correct.
How do the blog, glossary and cheat sheets fit into my learning routine?
Use them as satellites around your main course: read blog articles for context and motivation, hit the glossary the instant jargon appears, and keep cheat sheets open while coding. Together with study notes, Code Lab and the 24/7 AI Mentor, they turn passive reading into a complete, free learning system.

What Learners Say

Real journeys from the SkillVeris community — swipe for more.

SkillVeris taught me Python through Cricket. Now I’m building real projects and feeling confident!
Arjun S. · B.Tech Student
The best platform for hobby-based learning. Concepts finally stick.
Priya R. · Data Analyst
I went from zero coding to a portfolio of projects — all by learning through my love for gaming. Landed my first internship!
Kabir M. · CS Undergraduate
Trending Topics50 popular tags — tap to explore
Trending CoursesAll 37 free courses — tap to browse