﻿---
title: Decorators Pattern in Python
date: 2024-12-03
excerpt: Understand Python decorators as a design pattern, including first-class functions, simple wrappers, chained decorators, and common uses.
tags: [Python, Decorator]
cover: https://assets.vluv.space/cover/Lang/Python/python1.webp
updated: 2026-07-08 08:00:12
lang: en
i18n:
  cn: /decorators_pattern
  translation: 2
---

The decorator pattern is a design pattern that lets you add new behavior to an original object by placing it inside a wrapper object. It is an alternative to inheritance: a way to extend a class without subclassing it.

With decorators, you can modify the behavior of a function or method without directly changing its source code, making the code cleaner and more Pythonic.

## The fundamentals

### functions in python

Functions in Python are [first-class objects](https://en.wikipedia.org/wiki/First-class_citizen). They can be assigned to variables, passed as arguments, returned as values, and dynamically created or modified at runtime. Combined with built-in decorators, this makes it easy to implement cross-cutting concerns without an additional AOP library.

> [!NOTE] First-Class Object
>
> - being expressible as an anonymous literal value
> - being storable in variables
> - being storable in data structures
> - having an intrinsic identity (independent of any given name)
> - being comparable for equality with other entities
> - being passable as a parameter to a procedure/function
> - being returnable as the result of a procedure/function
> - being constructible at runtime
> - being printable
> - being readable
> - being transmissible among distributed processes
> - being storable outside running processes

Here are examples of using these function properties in Python:

```python
# 1. Assigning Functions to Variables
say_hello = greet
print(say_hello("Alice"))  # Output: Hello, Alice!


# 2. Pass function as an argument
def apply_function(func, value):
    return func(value)

def uppercase(text):
    return text.upper()

result = apply_function(uppercase, "hello")
print(result)  # Output: HELLO

# 3. Return function from another function (Nested Function)
def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

double = make_multiplier(2)
print(double(5))  # Output: 10
```

### Simple Demo

With the properties above, we can implement a simple decorator that measures execution time.

Tips: use `functools.wraps` to preserve metadata from the original function, such as `__name__` and `__doc__`.

```python
from functools import wraps
from time import time


def simple_decorator(func):
    @wraps(func)   # This annotation preserves metadata from func
    def wrapper(*args, **kwargs):
        print(f"function <{func.__name__}> is called")
        start_time = time()
        result = func(*args, **kwargs)
        print(f"Function execution time: {time() - start_time}")
        return result

    return wrapper


@simple_decorator
def calculate():
    sum = 0
    for i in range(1000000):
        sum += i
    print(sum)
# The code above is equivalent to:
# @simple_decorator syntax is equivalent to calculate = simple_decorator(calculate)

calculate()

[OUTPUT]
function <calculate> is called
499999500000
Function execution time: 0.16027212142944336
```

### Chaining Decorators

Python allows multiple decorators, written as `@decorator1` `@decorator2` ... `@decoratorN`.

```python

from functools import wraps


def star(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("*" * 30)
        func(*args, **kwargs)
        print("*" * 30)

    return wrapper

def hyphen(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("-" * 30)
        func(*args, **kwargs)
        print("-" * 30)

    return wrapper

@star
@hyphen
def greet(name):
    print(f"Hello, {name}!")

greet("Python")

[OUTPUT]
******************************
------------------------------
Hello, Python!
------------------------------
******************************
```

## Best Practices(?)

- Parameter Validation Decorators
- Method Routing
- Caching and Memoization
- ...

### @lru_cache

```python
from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)
```

### getter/setter

```python
class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        self._age = value

person = Person("Alice", 25)
print(person.name)  # Output: Alice
person.name = "Bob"
print(person.name)  # Output: Bob
```

## Ref

[What are "first-class" objects? —— StackOverFlow](https://stackoverflow.com/questions/245192/what-are-first-class-objects)
