在python中装饰器的使用
文章目录
- 一、前言
- 二、基本知识
- 2.1 闭包
- 2.2 装饰器
- 三、内置装饰器
- 3.1 @property
- 3.3 @classmethod
- 3.4 @staticmethod
- 四、自定义装饰器
- 4.1 装饰器函数
- 4.1.1 无参装饰器
- 4.1.2 含参装饰器
- 4.1.3 类方法装饰器
- 4.2 类装饰器
- 4.2.1 无参装饰器
- 4.2.2 含参装饰器
- 4.3 保留原函数信息
- 4.4. 多个装饰器
- 五、实践
一、前言
参考:https://pythonhowto.readthedocs.io/zh-cn/latest/decorator.html#id19
https://liaoxuefeng.com/books/python/functional/decorator/index.html
二、基本知识
2.1 闭包
闭包是嵌套函数,内部函数可以访问外部函数作用域的变量,他有两个显著特点:
- 嵌套函数
- 内部函数引用了外部函数作用域变量,并非全局变量
def outer():message = "Hello"def inner(): # 内部函数print(message) # 访问外部变量return innerclosure = outer()
closure() # 输出: Hello
2.2 装饰器
在python中,装饰器是一种增强现有函数行为的工具,在不修改原函数代码的前提下为函数动态增加新功能。
装饰器主要是利用了函数是一等公民,可以赋值给变量、作为参数传递或作为返回值的这一特性。
装饰器的工作原理:遇到类似@ decorator
语法时,会将被装饰的函数作为参数传递给装饰器函数,装饰器函数会返回一个新的函数来代替原始函数。
三、内置装饰器
3.1 @property
将方法转换为属性,实现安全访问和计算属性。如果不设置setter
方法就是可读属性
class Circle:def __init__(self, radius):self._radius = radius@propertydef radius(self):return self._radius # 直接通过 obj.radius 访问@radius.setterdef radius(self, value):if value < 0:raise ValueError("半径不能为负")self._radius = valuec = Circle(5)
print(c.radius) # 输出: 5 (直接像属性一样调用)
c.radius = 10 # 触发 setter 方法
3.3 @classmethod
定义类方法,第一个参数为类本身cls
,无需创建实例即可通过类直接调用
class Date:def __init__(self, year, month, day):self.year = yearself.month = monthself.day = day@classmethoddef from_string(cls, date_str): year, month, day = map(int, date_str.split("-"))return cls(year, month, day)date = Date.from_string("2023-10-01") # 通过类方法创建实例
3.4 @staticmethod
定义静态方法,无需实例或类参数
class MathUtils:@staticmethoddef add(a, b):return a + bprint(MathUtils.add(2, 3)) # 输出: 5 (直接通过类调用)
四、自定义装饰器
4.1 装饰器函数
4.1.1 无参装饰器
函数不带参数时可使用下面方式:
def my_decorator(func): # 被装饰的函数def wrapper():print("Before function call")func()print("After function call")return wrapper@my_decorator
def say_hello():print("Hello!")say_hello()
# 输出:
# Before function call
# Hello!
# After function call
除了使用@语法糖
,也可以手动装饰:
# 手动装饰
def say_hello():print("Hello!")decorated_func = my_decorator(say_hello)
decorated_func()
处理带参数的函数时可以使用*arg
和*kwargs
来接受任意参数
def decorator(func): # 被装饰的函数def wrapper(*args, **kwargs): # 函数参数print("Decorator work")return func(*args, **kwargs)return wrapper@decorator
def add(a, b):return a + bprint(add(2, 3)) # 输出: Decorator work → 5
4.1.2 含参装饰器
如果要在装饰器传参,需要三层嵌套函数:
def repeat(n_times): # 装饰器参数def decorator(func): # 被装饰的函数def wrapper(*args, **kwargs): # 函数参数for _ in range(n_times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator@repeat(n_times=3)
def greet(name):print(f"Hello {name}")greet("Alice")
# 输出 3 次: Hello Alice
4.1.3 类方法装饰器
类方法的函数装饰器和函数的函数装饰器类似
def decorator(func): def wrapper(*args, **kwargs):print("Decorator work")return func(*args, **kwargs)return wrapperclass TestDecorator():@decoratordef add(self,a, b):return a + bobj=TestDecorator()
print(obj.add(2, 3))
# Decorator work
# 5
4.2 类装饰器
4.2.1 无参装饰器
类装饰器使用__call__
方法将类的实例变成一个用于装饰器的方法
class CountCalls:def __init__(self, func):self.func = funcself.calls = 0def __call__(self, *args, **kwargs):self.calls += 1print(f"Call {self.calls} of {self.func.__name__}")return self.func(*args, **kwargs)@CountCalls
def say_hello():print("Hello!")say_hello() # 输出: Call 1 of say_hello → Hello!
say_hello() # 输出: Call 2 of say_hello → Hello!
4.2.2 含参装饰器
装饰器类的参数需要通过__init__
方法传递,所以被装饰的函数就只能在__call__
方法中传入,为了把函数的参数传入,就在__call__
方法中再封装一层
class CountCalls:def __init__(self, arg0):self.arg0 = arg0self.calls = 0def __call__(self, func):def wrapper(*args, **kwargs):self.calls += 1print(f"Call {self.calls} of {func.__name__}, {self.arg0}")return func(*args, **kwargs)return wrapper@CountCalls(arg0=0)
def say_hello():print("Hello!")say_hello() # 输出: Call 1 of say_hello, 0 → Hello!
say_hello() # 输出: Call 2 of say_hello, 0 → Hello!
4.3 保留原函数信息
装饰器会覆盖原函数的元信息,需要使用functools.wraps
来保留:
from functools import wrapsdef decorator(func):@wraps(func) # 关键点def wrapper(*args, **kwargs):"""Wrapper docstring"""print("Decorator action")return func(*args, **kwargs)return wrapper@decorator
def original():"""Original docstring"""passprint(original.__name__) # 输出: original
print(original.__doc__) # 输出: Original docstring
4.4. 多个装饰器
使用多个装饰器时,执行顺序按靠近函数的顺序依次执行
@decorator1 # 后执行
@decorator2 # 先执行
def my_func():pass
# 等价于: decorator1(decorator2(my_func))
五、实践
日志记录:
from functools import wrapsdef log_execution(func):@wraps(func)def wrapper(*args, **kwargs):print(f"Executing {func.__name__}...")result = func(*args, **kwargs)print(f"Finished executing {func.__name__}")return resultreturn wrapper@log_execution
def hello(name):print(f"hello {name}")hello("Lucy")
# Executing hello...
# hello Lucy
# Finished executing hello
重试机制:
import time
from functools import wrapsdef retry(max_attempts=3, delay=1):def decorator(func):@wraps(func)def wrapper(*args, **kwargs):attempts = 0while attempts < max_attempts:try:return func(*args, **kwargs)except Exception as e:print(f"尝试 {attempts+1}/{max_attempts} 失败: {e}")attempts += 1time.sleep(delay)raise RuntimeError("所有重试均失败")return wrapperreturn decorator@retry(max_attempts=2)
def call_unstable_api():import randomif random.random() < 0.8:raise ConnectionError("API 不可用")return "成功"call_unstable_api() # 最多重试 2 次