一、基础知识
1. 什么是 Python 装饰器
装饰器(Decorator)是 Python 中的一种设计模式,允许你在不修改原函数代码的情况下,动态地添加或修改函数的行为。简单来说,装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数。
装饰器常用于以下几种场景:
- 添加日志:记录函数调用的时间、参数等。
- 权限检查:检查用户是否有权限执行某个操作。
- 缓存:对函数结果进行缓存,避免重复计算。
- 验证输入输出:对函数的输入输出进行检查和处理。
2. 装饰器的基本语法
装饰器的基本语法使用了 Python 的 @
符号。装饰器函数应用于另一个函数或方法。
@decorator
def function_to_decorate():
pass
这个语法等同于:
function_to_decorate = decorator(function_to_decorate)
3. 如何编写装饰器
一个简单的装饰器是一个接受函数作为参数,并返回一个新的函数的函数。例如,下面的装饰器 my_decorator
会在函数执行前打印一条消息:
def my_decorator(func):
def wrapper():
print("Function is being called.")
return func()
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello() # 调用装饰后的函数
输出:
Function is being called.
Hello!
4. 解释:
my_decorator
是一个装饰器,它接受say_hello
函数作为参数。wrapper
是装饰器内部定义的函数,它在调用say_hello
函数之前打印一条消息。@my_decorator
语法相当于将say_hello
函数传递给my_decorator
,然后my_decorator
返回一个新的函数(即wrapper
)。
5. 带参数的装饰器
如果被装饰的函数有参数,我们需要修改装饰器以处理这些参数。我们可以通过 *args
和 **kwargs
来捕获传递给被装饰函数的任何参数。
def my_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling function {func.__name__} with arguments {args} and keyword arguments {kwargs}")
return func(*args, **kwargs)
return wrapper
@my_decorator
def add(a, b):
return a + b
print(add(2, 3)) # 调用带参数的函数
输出:
Calling function add with arguments (2, 3) and keyword arguments {}
5
6. 装饰器带有返回值
装饰器不仅可以修改函数的输入输出,还可以在函数执行前后对结果进行处理。比如,我们可以让装饰器修改函数的返回值:
def my_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2 # 修改返回值
return wrapper
@my_decorator
def multiply(a, b):
return a * b
print(multiply(2, 3)) # 返回值会被修改
输出:
12
7. 装饰器的嵌套
装饰器不仅可以一个接一个地应用于一个函数,还可以将多个装饰器嵌套使用。装饰器应用的顺序是从下到上。
def decorator_one(func):
def wrapper(*args, **kwargs):
print("Decorator One")
return func(*args, **kwargs)
return wrapper
def decorator_two(func):
def wrapper(*args, **kwargs):
print("Decorator Two")
return func(*args, **kwargs)
return wrapper
@decorator_one
@decorator_two
def say_hello():
print("Hello!")
say_hello()
输出:
Decorator One
Decorator Two
Hello!
在这个例子中,say_hello
函数首先被 decorator_two
装饰,然后被 decorator_one
装饰。所以它们的执行顺序是从内到外。
8. 使用 functools.wraps
保持原函数的元数据
当你使用装饰器时,装饰器函数会替换掉原函数,因此原函数的名称、文档字符串等信息会丢失。为了保留这些元数据,可以使用 functools.wraps
装饰器。
from functools import wraps
def my_decorator(func):
@wraps(func) # 保持原函数的元数据
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def say_hello():
"""This is a hello function."""
print("Hello!")
print(say_hello.__name__) # 输出原函数的名字
print(say_hello.__doc__) # 输出原函数的文档字符串
输出:
say_hello
This is a hello function.
9. 类方法和静态方法的装饰器
装饰器不仅可以应用于普通函数,也可以应用于类的方法。特别是类方法(@classmethod
)和静态方法(@staticmethod
)的装饰器应用。
def decorator(func):
def wrapper(self, *args, **kwargs):
print("Decorator applied!")
return func(self, *args, **kwargs)
return wrapper
class MyClass:
@decorator
def instance_method(self):
print("Instance method called!")
@staticmethod
@decorator
def static_method():
print("Static method called!")
obj = MyClass()
obj.instance_method() # 装饰器应用于实例方法
MyClass.static_method() # 装饰器应用于静态方法
输出:
Decorator applied!
Instance method called!
Decorator applied!
Static method called!
10. 总结
- 装饰器 是一种函数,它可以在不修改原函数代码的情况下,动态地修改或增强函数的行为。
- 装饰器通常用
@decorator
语法来应用。 - 可以使用
*args
和**kwargs
来处理带有参数的函数。 - 通过
functools.wraps
可以保留原函数的元数据。 - 装饰器可以用于普通函数、类方法、静态方法等。
二、场景
(一) @login_required
根据 token 验证用户是否已登录,并且在登录状态下,向被装饰的函数传递相关的用户信息。装饰器的目标是确保只有经过认证的用户才能访问某些功能,如果没有登录或认证失败,则会返回一个登录重定向的响应。
详细流程:
1. 装饰器函数 login_required
def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return wrapper
login_required
是一个装饰器函数,接受一个函数func
作为参数。@wraps(func)
装饰器用于保留原函数func
的元数据(例如函数名称、文档字符串等),确保装饰器不会破坏原函数的属性。wrapper
是包装函数,它将在login_required
装饰器调用时执行。在这个函数内,我们将执行登录验证逻辑并在成功验证后调用原始函数func
。
2. 获取请求中的 token
req = args[1]
token = req.GET.get('token') or req.headers.get(settings.OSS_TOKEN_HEADER_NAME) or req.COOKIES.get(settings.OSS_TOKEN_COOKIE_NAME)
- 从
args
中提取请求对象req
,请求对象是作为第二个参数传递给装饰器的。 - 尝试从不同的地方获取用户的认证
token
:
- 从请求的查询参数
req.GET
获取token
。 - 如果没有找到,则从请求头
req.headers
获取一个指定的令牌(settings.OSS_TOKEN_HEADER_NAME
)。 - 如果还没有找到,则尝试从请求的 cookies 中获取
token
(settings.OSS_TOKEN_COOKIE_NAME
)。
3. 如果没有 token
,返回登录重定向
login_res = get_redirect_login_res()
if not token:
return login_res
- 如果没有从请求中获取到
token
,则调用get_redirect_login_res()
获取登录重定向的响应,并返回这个响应。
4. 调用 UserCenterRequest.get_user_info
获取用户信息
headers = {
'Access-Token': token
}
try:
res = UserCenterRequest.get_user_info(headers=headers)
except BusinessException as e:
logger.exception('登录认证失败:{}'.format(e))
return login_res
- 如果获取到了
token
,则构造一个请求头,其中包含Access-Token
,并调用外部服务UserCenterRequest.get_user_info()
来获取用户信息。 - 如果在获取用户信息时发生了
BusinessException
异常(例如认证失败、token 无效等),则记录异常日志,并返回登录重定向响应login_res
。
5. 提取用户信息并将用户信息传递给被装饰的函数
customer_id = res['id']
kwargs['data']['customer_id'] = customer_id
- 如果获取用户信息成功,提取用户信息添加到
kwargs['data']
中
6. 调用原始函数
result = func(*args, **kwargs)
return result
- 在将用户信息添加到
kwargs
后,调用原始的func
函数,并将修改后的args
和kwargs
传递给它。 - 返回
func
的执行结果。
(二) @response
参数进来先校验,然后往下走直到result = func(*args, **kwargs),这个 result 就是 view 方法中 return 的数据。走到 func 时就执行 view 中的方法,直到 view 执行完 return 数据,然后再根据 return 数据的 code、异常等处理结果返回到前端
详细流程:
1. 装饰器定义
def response(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return wrapper
response
是一个装饰器函数,接受一个函数func
作为参数。@wraps(func)
装饰器用于保持原函数的元数据(如名称、文档字符串等),这有助于调试和文档生成。wrapper
是一个包装函数,它包含了原函数func
被调用前后的逻辑,*args
和**kwargs
是用来接收传递给原函数的所有参数。
2. 处理请求参数和序列化
seria_class = args[0]
req = args[1]
- 从
args
中提取第一个参数seria_class
和第二个参数req
。这些参数分别表示序列化类和请求对象。
接下来的代码检查 seria_class
是否有 serializer_class
属性,如果有,则尝试序列化请求数据:
if seria_class.serializer_class:
try:
if req.query_params:
serialize = seria_class.get_serializer(data=req.query_params)
else:
serialize = seria_class.get_serializer(data=req.data)
- 如果
seria_class.serializer_class
存在,表示需要对请求数据进行序列化。 - 如果请求包含查询参数 (
req.query_params
),则使用查询参数进行序列化;否则,使用请求体中的数据 (req.data
)。
3. 序列化验证
if serialize.is_valid():
data = serialize.data
kwargs['data'] = data
result = func(*args, **kwargs)
resp = {
'code': result.code,
'code_msg': result.code_msg,
'message': result.message,
'data': result.data
}
return Response(data=resp, status=status.HTTP_200_OK)
- 调用
serialize.is_valid()
验证序列化的数据是否合法。如果有效,将序列化后的数据存储在data
变量中。 kwargs['data'] = data
将处理后的数据作为关键字参数传递给原函数。- 调用原函数
func(*args, **kwargs)
,并根据返回的结果构建一个标准化的响应字典resp
。 - 最后,使用
Response
对象返回成功响应。
4. 序列化验证失败的处理
else:
resp = {
'code': ReturnCodeMsg.PARAM_ERROR['code'],
'code_msg': ReturnCodeMsg.PARAM_ERROR['msg'],
'message': 'param error',
'data': serialize.errors
}
return Response(data=resp, status=status.HTTP_400_BAD_REQUEST)
- 如果序列化验证失败,返回一个包含错误信息的响应。
5. 如果 serializer_class
不存在,直接执行原函数
else:
result = func(*args, **kwargs)
resp = {
'code': result.code,
'code_msg': result.code_msg,
'message': result.message,
'data': result.data
}
return Response(data=resp, status=status.HTTP_200_OK)
- 如果
seria_class
没有serializer_class
属性,跳过序列化验证,直接执行原函数。 - 生成标准的响应字典,并返回成功响应。
6. 异常处理
一、捕获 BusinessException
except BusinessException as e:
msg = u'%s exception:%s' % (func.__name__, e)
logger.info(msg, exc_info=True)
resp = {
'code': e.code,
'code_msg': e.code_msg,
'message': str(e)
}
return Response(data=resp, status=status.HTTP_200_OK)
- 如果抛出了
BusinessException
(业务异常),则记录日志并返回包含异常信息的标准响应。
二、捕获其他异常
except Exception as e:
msg = 'response get except, msg: {}'.format(e)
logger.info(msg, exc_info=True)
resp = {
'code': ReturnCodeMsg.UNKNOWN_ERROR['code'],
'code_msg': ReturnCodeMsg.UNKNOWN_ERROR['msg'],
'message': '{}'.format(e)
}
return Response(data=resp, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
- 如果发生其他未知的异常,记录错误日志并返回包含错误信息的响应。