文章目录
一、前言
参考: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 inner
closure = outer()
closure() # 输出: Hello
2.2 装饰器
在python中,装饰器是一种增强现有函数行为的工具,在不修改原函数代码的前提下为函数动态增加新功能。
装饰器主要是利用了函数是一等公民,可以赋值给变量、作为参数传递或作为返回值的这一特性。
装饰器的工作原理:遇到类似@ decorator
语法时,会将被装饰的函数作为参数传递给装饰器函数,装饰器函数会返回一个新的函数来代替原始函数。
三、内置装饰器
3.1 @property
将方法转换为属性,实现安全访问和计算属性。如果不设置setter
方法就是可读属性
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius # 直接通过 obj.radius 访问
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("半径不能为负")
self._radius = value
c = Circle(5)
print(c.radius) # 输出: 5 (直接像属性一样调用)
c.radius = 10 # 触发 setter 方法
3.3 @classmethod
定义类方法,第一个参数为类本身cls
,无需创建实例即可通过类直接调用
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def 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:
@staticmethod
def add(a, b):
return a + b
print(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 + b
print(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 result
return wrapper
return 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 wrapper
class TestDecorator():
@decorator
def add(self,a, b):
return a + b
obj=TestDecorator()
print(obj.add(2, 3))
# Decorator work
# 5
4.2 类装饰器
4.2.1 无参装饰器
类装饰器使用__call__
方法将类的实例变成一个用于装饰器的方法
class CountCalls:
def __init__(self, func):
self.func = func
self.calls = 0
def __call__(self, *args, **kwargs):
self.calls += 1
print(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 = arg0
self.calls = 0
def __call__(self, func):
def wrapper(*args, **kwargs):
self.calls += 1
print(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 wraps
def decorator(func):
@wraps(func) # 关键点
def wrapper(*args, **kwargs):
"""Wrapper docstring"""
print("Decorator action")
return func(*args, **kwargs)
return wrapper
@decorator
def original():
"""Original docstring"""
pass
print(original.__name__) # 输出: original
print(original.__doc__) # 输出: Original docstring
4.4. 多个装饰器
使用多个装饰器时,执行顺序按靠近函数的顺序依次执行
@decorator1 # 后执行
@decorator2 # 先执行
def my_func():
pass
# 等价于: decorator1(decorator2(my_func))
五、实践
日志记录:
from functools import wraps
def 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 result
return wrapper
@log_execution
def hello(name):
print(f"hello {name}")
hello("Lucy")
# Executing hello...
# hello Lucy
# Finished executing hello
重试机制:
import time
from functools import wraps
def retry(max_attempts=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
print(f"尝试 {attempts+1}/{max_attempts} 失败: {e}")
attempts += 1
time.sleep(delay)
raise RuntimeError("所有重试均失败")
return wrapper
return decorator
@retry(max_attempts=2)
def call_unstable_api():
import random
if random.random() < 0.8:
raise ConnectionError("API 不可用")
return "成功"
call_unstable_api() # 最多重试 2 次