Python Decorators

Let’s look at decorators

  1. Notice

    A decorator takes a callable and supplies its replacement. A wrapper can do work before or after delegating to the original function.

  2. Try

    Follow the example, change its sample, and compare what happens.

  3. Check

    Explain the result and test your understanding.

Watch how it works

Focus on how the names, objects, and calculations change. We’ll inspect the printed output in the next section.

Explore the idea

Follow the code and watch its state change. Guided simulation.

Code
Python
def announce(function):
    def wrapper():
        print("before")
        function()
        print("after")
    return wrapper
@announce
def greet():
    print('hello')
greet()
Live state

Top-level scope

announce→Object 1
greet→Object 3
Object 1 · functionfunction announceObject 3 · functionfunction wrapper
This step

A decorator takes a callable and supplies its replacement. A wrapper can do work before or after delegating to the original function.

Picture decorators

A decorator takes a callable and supplies its replacement. A wrapper can do work before or after delegating to the original function.

Image brief

Alt text
A diagram for Python Decorators. Instructions: def announce(function): def wrapper(): print("before") function() print("after") return wrapper @announce def greet(): print('hello') greet(). Displayed result: before hello after. The final example bindings are announce --> object 1 (function); greet --> object 3 (function). object 1: function announce; object 3: function wrapper
Image generation prompt
Create a clean educational illustration for a Python programming lesson titled "Python Decorators". Teaching focus: A decorator takes a callable and supplies its replacement. A wrapper can do work before or after delegating to the original function. Depict exactly the relationships and labels in this reference diagram: EXAMPLE INSTRUCTIONS def announce(function): def wrapper(): print("before") function() print("after") return wrapper @announce def greet(): print('hello') greet() | v DISPLAYED RESULT before hello after FINAL BINDINGS announce --> object 1 (function) greet --> object 3 (function) object 1: function announce object 3: function wrapper Use a flat vector style, generous spacing, clear arrows and legible labels, with a restrained blue/teal palette on a plain background. The diagram must explain the programming idea accurately. Do not depict variables as boxes containing copied objects when they share a reference. Avoid mascots, decorative screenshots, logos, gradients and extra text. Landscape composition, approximately 3:2, suitable for a course card. Accessible description: A diagram for Python Decorators. Instructions: def announce(function): def wrapper(): print("before") function() print("after") return wrapper @announce def greet(): print('hello') greet(). Displayed result: before hello after. The final example bindings are announce --> object 1 (function); greet --> object 3 (function). object 1: function announce; object 3: function wrapper

Follow the execution

Choose a different sample input, then follow every step through to the output. Before revealing it, predict what the program will display.

Execution workspace

Choose a sample and follow its execution. Guided simulation.

Code
Python
def announce(function):
    def wrapper():
        print("before")
        function()
        print("after")
    return wrapper
@announce
def greet():
    print('hello')
greet()
Live state

Top-level scope

announce→Object 1
greet→Object 3
Object 1 · functionfunction announceObject 3 · functionfunction wrapper
This step

The example has finished. Compare the final output with the values and references above.

Terminal
Output
before
hello
after

What do you expect this program to print?

What does this example display?

def announce(function):
    def wrapper():
        print("before")
        function()
        print("after")
    return wrapper
@announce
def greet():
    print('hello')
greet()
What does this example display?
Choose one answer. You can try again.

Answer and explanation

before hello after

Follow the printed values in order. The output is: before hello after

Which explanation matches what you learned?

Which explanation matches what you learned?
Choose one answer. You can try again.

Answer and explanation

A decorator takes a callable and supplies its replacement. A wrapper can do work before or after delegating to the original function.

A decorator takes a callable and supplies its replacement. A wrapper can do work before or after delegating to the original function.

Decorators

Decorators in Python allows you to modify the behavior of functions or classes. If you're coming from a beginner’s perspective, think of a decorator as a "wrapper" that adds extra features to an existing function without changing its core code.

Why Use Decorators?

Imagine you want to log the execution time of various functions or check user authentication before executing a task. You could repeat that logic inside every function... or, you could just use a decorator. It keeps your code clean, modular, and reusable.

Functions are First-Class Citizens

Before diving into decorators, it’s important to know that in Python, functions are first-class objects. This means:

  • You can pass a function as an argument.
  • You can return a function from another function.
  • You can assign a function to a variable.
def greet():
    return "Hello!"

say_hello = greet
print(say_hello())
Hello!

Basic Decorator Structure

A decorator is simply a function that takes another function as input and returns a new function with added behavior.

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

def say_hi():
    print("Hi!")

decorated = my_decorator(say_hi)
decorated()
Something is happening before the function is called.
Hi!
Something is happening after the function is called.

Using the @ Syntax (Syntactic Sugar)

Python provides a cleaner way to apply decorators using the @ symbol.

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper
    
@my_decorator
def say_hi():
    print("Hi!")

say_hi()
Something is happening before the function is called.
Hi!
Something is happening after the function is called.

Decorators with Arguments

If your target function has parameters, your wrapper needs to accept them too.

def log_args(func):
    def wrapper(*args, **kwargs):
        print(f"Arguments passed: {args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log_args
def add(x, y):
    return x + y

print(add(5, 7))
Arguments passed: (5, 7), {}
12

Returning Values from Decorated Functions

Always remember to return the result of the original function if it produces one.

Using functools.wraps

When you use decorators, the metadata of the original function (like its name or docstring) can get lost. To preserve it, use functools.wraps.

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("Before function call")
        result = func(*args, **kwargs)
        print("After function call")
        return result
    return wrapper

@my_decorator
def greet(name):
    "Greets a user by name"
    print(f"Hello, {name}")

print(greet.__name__)
print(greet.__doc__)
Before function call
Hello, Alice
After function call
greet
Greets a user by name

Common Use Cases of Decorators

  • Logging
  • Access control (authentication/authorization)
  • Timing functions
  • Memoization (caching)
  • Validation

Built-in Decorators in Python

Python includes several built-in decorators:

  • @staticmethod
  • @classmethod
  • @property
class MyClass:
    @staticmethod
    def static_method():
        print("This is a static method.")

    @classmethod
    def class_method(cls):
        print(f"This is a class method from {cls.__name__}")

    @property
    def name(self):
        return "TutorialKart"

obj = MyClass()
obj.static_method()
obj.class_method()
print(obj.name)
This is a static method.
This is a class method from MyClass
TutorialKart

Best Practices & Things to Watch For

  • Always use functools.wraps when writing decorators.
  • Make sure your decorator does not block or break return values.
  • Understand that decorators are executed at the time of function definition, not at runtime.
  • Use decorators to separate logic concerns (e.g., don’t mix validation and logging inside the core function).

Verification Steps

To ensure your decorator works as expected:

  1. Call the decorated function and check if the new behavior is added.
  2. Check that arguments and return values are intact.
  3. Verify metadata using __name__ and __doc__.
  4. Use logging statements in the decorator to debug step-by-step.

Conclusion

Decorators are one of Python’s elegant ways to inject functionality without altering existing code.

What to remember

Connect what you noticed in the example with the idea below.

Remember

A decorator takes a callable and supplies its replacement. A wrapper can do work before or after delegating to the original function.

Watch out for this idea

A decorator can only change a function’s name.

This is the misconception to avoid.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Python Email Tutorial →