装饰器是语法糖: 在代码中利用更简洁流畅的语法实现更为复杂的功能。
装饰器允许包装另一个函数,以扩展包装函数的行为,而无需修改基础函数定义。
经常会用到 @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的类都必须实现这两个方法,从而在不同的形状之间保持了一致的接口。
参考