﻿---
title: Decorators Pattern in Python
date: 2024-12-03
excerpt: Python 装饰器模式
tags: [Python, Decorator]
cover: https://assets.vluv.space/cover/Lang/Python/python1.webp
---

装饰器模式(Decorator Pattern)是一种设计模式，它允许你通过将对象放入包装对象中来为原对象添加新的行为。装饰器模式是一种替代继承的技术，它通过一种无需子类化增加功能的方式来扩展类的功能。

使用 decorators，可以在不直接修改源码的情况下修改 function 或 method 的功能，从而让代码更简洁，更 Pythonic

## The fundamentals

### functions in python

python 中函数是 [first-class object](https://en.wikipedia.org/wiki/First-class_citizen)，可以赋值给变量、作为参数传递、作为返回值、在运行时动态创建和修改，结合内置的装饰器，可以方便地实现横切关注点，无需额外的 AOP 库。

> [!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

如下是使用 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

结合以上特性，可以实现一个简单的装饰器，实现计时功能

Tips: 建议使用 `functools.wraps` 保留原函数的元信息，如`__name__、__doc__` 等

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


def simple_decorator(func):
    @wraps(func)   # 此注解用于保留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)
# 上述代码等价于
# @simple_decorator syntax 等价于 calculate = simple_decorator(calculate)

calculate()

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

### Chaining Decorators

Python 允许使用多个装饰器，可以通过 `@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)
