装饰器是语法糖: 在代码中利用更简洁流畅的语法实现更为复杂的功能。

装饰器允许包装另一个函数,以扩展包装函数的行为,而无需修改基础函数定义。

经常会用到 @staticmethod 和 @classmethod 两个内置装饰器。

常见装饰器 @staticmethod 和 @classmethod

静态方法通过@staticmethod装饰器定义,它不接收隐含的self或cls参数。这类方法通常与类的实例无关,可以视为与类关联的函数,用于组织逻辑或提供工具函数 ,提升代码模块化。

1
2
3
4
5
6
7
class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

result = MathUtils.add(5, 3)
print(result)  # 输出: 8

类方法通过 @classmethod 装饰器定义,它接收一个隐含的参数cls ,代表类本身而不是类的实例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Pizza:
    toppings = ["cheese", "tomato"]

    @classmethod
    def add_default_topping(cls, topping):
        cls.toppings.append(topping)

    @classmethod
    def get_available_toppings(cls):
        return cls.toppings

Pizza.add_default_topping("mushroom")
print(Pizza.get_available_toppings())  # 输出: ['cheese', 'tomato', 'mushroom']

函数装饰器

函数装饰器包装另一个函数,返回一个函数

装饰器详解

装饰不带参数的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

@my_decorator
def greet():
    print('hello world')

greet()

# 没有装饰器时,等价于如下调用:
# greet = my_decorator(greet)
# greet()

装饰带一个参数的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def my_decorator(func):
    def wrapper(message):
        print('wrapper of decorator')
        func(message)
    return wrapper


@my_decorator
def greet(message):
    print(message)

greet('hello world')

# 没有装饰器时,等价于如下调用:
# greet = my_decorator(greet)
# greet('hello world')

装饰带不定长参数的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def my_decorator(func):
    def wrapper(message, *args, **kwargs):  # 不定长参数*args,**kwargs
        print('wrapper of decorator')
        func(message, *args, **kwargs)

    return wrapper

@my_decorator
def greet(message):
    print(message)

greet('hello world')

@my_decorator
def greet2(message, num=2):
    print(message, num)

greet2("hello world2")

装饰器带参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def repeat(num):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(num):
                print('wrapper of decorator')
                func(*args, **kwargs)
        return wrapper
    return my_decorator


@repeat(4)
def greet(message):
    print(message)

# @语法糖等价于:
# my_decorator = repeat(4)
# greet = my_decorator(greet)

greet('hello world')

decorator库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from decorator import decorator

@decorator
def repeat(func, name=None, *args, **kwargs):
    print('wrapper of decorator', name)
    func(*args, **kwargs)

@repeat(name="greet")
def greet(message):
    print(message)

# @语法糖等价于:
# greet = repeat(greet, name="greet")

greet('hello world')

保留原函数的元信息

打印出 greet() 函数的一些元信息:

1
2
3
4
5
6
7
8
9
greet.__name__
## 输出
'wrapper'

help(greet)
# 输出
Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

输出的是装饰器,可以通常使用内置的装饰器@functools.wrap,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)。

1
2
3
4
5
6
7
8
import functools

def my_decorator(func):
    @functools.wraps(func) # 保留原函数元信息
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper

类装饰器

定义一个类装饰器,装饰函数,默认调用__call__方法

本身无参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Count:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print('num of calls is: {}'.format(self.num_calls))
        return self.func(*args, **kwargs)

@Count
def example():
    print("hello world")

# 等价于example = Count(example) 

example()

带参数的类装饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Count:
    def __init__(self, a, *args, **kwargs): # 类装饰器参数
        self.a = a
        self.num_calls = 0

    def __call__(self, func): # 被装饰函数
        print(self.a)

        def wrapper(*args, **kwargs):
            print(self.a)
            self.num_calls += 1
            print('num of calls is: {}'.format(self.num_calls))
            return func(*args, **kwargs)
        return wrapper


@Count("aaaa")
def example():
    print("hello world")


print("开始调用example函数..............")

example()

# @语法糖等价形式
# example = Count("aaaa")(example)

描述符与装饰器

还有一类装饰器比较特殊,比如python描述符类的property。

在Python中,属性描述符可以用作装饰器,是因为描述符对象可以通过实现 
__get__()、__set__() 和 __delete__() 方法来拦截对属性的访问和操作。 
https://docs.python.org/3/reference/datamodel.html#descriptors
https://docs.python.org/3/howto/descriptor.html#descriptorhowto

关于描述符,这里不赘述。基于描述符,我们也可以实现自定义的property:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class cached_property:
    def __init__(self, func):
        self.func = func
        self.name = func.__name__
        self.cache = {}

    def __get__(self, instance, owner):
        if instance is None:
            return self
        if self.name not in self.cache:
            value = self.func(instance)
            self.cache[self.name] = value
        return self.cache[self.name]

    def __set__(self, instance, value):
        raise TypeError("can't set attribute")


class MyClass:
    @cached_property
    # @property
    def value(self):
        print("Calculating value...")
        return 42


obj = MyClass()
print(obj.value)  # 输出: Calculating value...   42

# 使用缓存的值
print(obj.value)  # 输出: 42

# 尝试修改属性值(抛出TypeError)
obj.value = "invalid"  # 抛出 TypeError: can't set attribute

property装饰器

property装饰器允许将方法转变为属性访问的形式,从而提供更简洁、更符合面向对象原则的接口。它支持定义getter、setter和deleter方法,而外部调用时就像直接操作属性一样。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Celsius:
    def __init__(self, temperature=0):
        self._temperature = temperature

    @property
    def temperature(self):
        return self._temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible")
        self._temperature = value

c = Celsius()
c.temperature = 37  # 使用setter设置温度
print(c.temperature)  # 使用getter获取温度 ,输出: 37

属性访问控制

通过property ,可以轻松实现属性访问控制 ,比如检查数据的有效性、执行额外逻辑等,而无需暴露内部实现细节。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Account:
    def __init__(self, balance=0):
        self._balance = balance

    @property
    def balance(self):
        """返回账户余额"""
        return self._balance

    @balance.setter
    def balance(self, amount):
        """设置账户余额,不允许负数存款"""
        if amount < 0:
            raise ValueError("Balance cannot be negative")
        self._balance = amount

account = Account()
account.balance = 100  # 正常设置
print(account.balance)  # 输出: 100

try:
    account.balance = -50  # 尝试设置负数 ,将抛出异常
except ValueError as e:
    print(e)  # 输出: Balance cannot be negative

自定义setter与getter

property还允许自定义的getter和setter逻辑,这样可以在获取或设置属性值时执行额外的操作 ,如日志记录、单位转换等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class TemperatureConverter:
    def __init__(self, kelvin):
        self._kelvin = kelvin

    @property
    def kelvin(self):
        return self._kelvin

    @property
    def celsius(self):
        """从Kelvin转换到Celsius"""
        return self._kelvin - 273.15

    @celsius.setter
    def celsius(self, value):
        """设置Celsius温度 ,同时更新Kelvin"""
        self._kelvin = value + 273.15

converter = TemperatureConverter(273.15)
print(converter.celsius)  # 输出: 0.0

converter.celsius = 100  # 设置摄氏温度
print(converter.kelvin)  # 输出: 373.15,由于setter更新了_kelvin

通过这些示例可以看出,property装饰器为属性访问提供了优雅且强大的控制手段,使得代码更加整洁、易于理解和维护。

装饰类的装饰器

在Python中,装饰类的装饰器是一种特殊类型的函数,它用于修改或增强类的行为。装饰器可以在不修改原始类定义的情况下,通过将类传递给装饰器函数来对其进行装饰。

通常情况下,装饰类的装饰器是一个接受类作为参数的函数,并返回一个新的类或修改原始类的函数。这个装饰器函数可以在类定义之前使用@符号应用到类上。

输入是类,输出也是类。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time


def timer_decorator(cls):
    class TimerClass(cls):
        def __getattribute__(self, name):
            start_time = time.time()
            result = super().__getattribute__(name)
            end_time = time.time()
            execution_time = end_time - start_time
            print(f"Method '{name}' executed in {execution_time} seconds.")
            return result

    return TimerClass


@timer_decorator
class MyClass:
    def my_method(self):
        time.sleep(1)
        print("Executing my_method")


obj = MyClass()
obj.my_method()

使用 wrapt 模块编写更扁平的装饰器

函数签名是固定的,必须是(wrapped, instance, args, kwargs)

1
2
3
4
5
6
7
8
9
import wrapt

@wrapt.decorator
def pass_through(wrapped, instance, args, kwargs):
    return wrapped(*args, **kwargs)

@pass_through
def function():
    pass

更多使用见 装饰器详解

单分派泛函:singledispatch

singledispatch装饰器实现了通用函数的概念 ,允许为不同类型的参数注册不同的处理函数。当调用这样的函数时,它会自动根据第一个参数的类型来选择对应的实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from functools import singledispatch

@singledispatch
def process(value):
    """默认处理函数 ,用于未注册的类型"""
    print(f"Default processing for type {type(value).__name__}: {value}")

@process.register(int)
def _(value: int):
    """处理整数类型"""
    print(f"Processing integer: {value * 2}")

@process.register(str)
def _(value: str):
    """处理字符串类型"""
    print(f"Processing string: {value.upper()}")

# 示例调用
process(10)       # 输出: Processing integer: 20
process("hello")  # 输出: Processing string: HELLO
process(3.14)     # 输出: Default processing for type float: 3.14

装饰器实现重载

动态创建函数实现重载

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def overload_decorator(*signatures):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for sig_func in signatures:
                if all(isinstance(arg, sig_arg_type) for arg, sig_arg_type in zip(args, sig_func)):
                    return sig_func(*args, **kwargs)
            raise TypeError("No matching signature found.")
        return wrapper
    return decorator

@overload_decorator
def process(value):
    """默认处理逻辑"""
    print(f"默认处理: {value}")

@overload_decorator((int,))
def process(value: int):
    """处理整数类型"""
    print(f"处理整数: {value * 2}")

@overload_decorator((str,))
def process(value: str):
    """处理字符串类型"""
    print(f"处理字符串: {value.upper()}")

# 示例调用
process(10)      # 输出: 处理整数: 20
process("hello") # 输出: 处理字符串: HELLO

这个overload_decorator装饰器接收一组签名(每个签名是一个元组,包含期望的参数类型),并根据传入的参数类型自动选择正确的函数版本来执行。如果找不到匹配的签名 ,将抛出TypeError异常。

第三方库typing_extensions

借助第三方库typing_extensions中的@overload装饰器,实现函数重载。

安装

1
pip install typing_extensions
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from typing_extensions import overload

@overload
def calculate(a: int, b: int) -> int:
    ...

@overload
def calculate(a: float, b: float) -> float:
    ...

def calculate(a, b):
    return a + b

# 实际调用
print(calculate(1, 2))    # 输出: 3
print(calculate(1.5, 2.5))  # 输出: 4.0

@overload装饰器本身不会改变函数的行为,它主要服务于类型检查器 。

模块approach: multipledispatch @dispatch 装饰器

根据函数参数的类型实现多路派发,即更高级别的重载功能。

安装

1
pip install multipledispatch
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from multipledispatch import dispatch

@dispatch(int)
def calculate(a):
    return a * 2

@dispatch(int, int)
def calculate(a, b):
    return a + b

@dispatch(float, float)
def calculate(a, b):
    return a * b

@dispatch(object, object)
def calculate(a, b):
    return f"不支持的操作: {a} 和 {b}"

# 调用示例
print(calculate(10))          # 输出: 20
print(calculate(5, 3))        # 输出: 8
print(calculate(2.5, 3.5))     # 输出: 8.75
print(calculate("hello", 5))  # 输出: 不支持的操作: hello 和 5

calculate函数根据传入参数的类型和数量自动选择了不同的处理逻辑。multipledispatch库通过分析函数签名,智能地匹配最适合的实现版本

抽象基类装饰器 @abstractmethod

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * (self.radius ** 2)

    def perimeter(self):
        return 2 * 3.14 * self.radius

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

    def perimeter(self):
        return 4 * self.side

shapes = [Circle(5), Square(10)]
for shape in shapes:
    print(f"Area: {shape.area()}, Perimeter: {shape.perimeter()}")

通过定义Shape抽象基类并声明area和perimeter为抽象方法,确保了任何继承自Shape的类都必须实现这两个方法,从而在不同的形状之间保持了一致的接口。

参考