首页 > 编程语言 >Python基础--装饰器

Python基础--装饰器

时间:2024-11-26 16:57:44浏览次数:11  
标签:return 函数 Python args -- func kwargs 装饰

一、基础知识

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 中获取 tokensettings.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 函数,并将修改后的 argskwargs 传递给它。
  • 返回 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)
  • 如果发生其他未知的异常,记录错误日志并返回包含错误信息的响应。

标签:return,函数,Python,args,--,func,kwargs,装饰
From: https://www.cnblogs.com/Studywith/p/18570492

相关文章

  • leetcode 53. 最大子数组和
    53.最大子数组和给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。 法一1.假如全是负数,那就是找最大值即可,因为负数肯定越加越大。2.如果有正数,则肯定从正数开始计算和,不然前面有负值,......
  • mysql基础用法
    1、数据库操作、表操作#显示所有数据库showdatabases#创建数据库:createdatabase数据库名CREATEdatabasestudy_mysql#使用数据库usestudy_mysql#创建表:createtable表名()comment注释CREATETABLEcreate_table_test(#字段名字段类型comment注释......
  • 如何通过看板提升产研团队的协作与产品研发速度?
    有没有过这样的经历?作为产品研发团队的一员,你每天在无数的需求、任务和反馈中奔忙,却总觉得事情像脱了缰的野马——需求来得快,解决得慢;计划定得满,执行跟不上。其实,这并不是因为团队不够努力,而是缺少一套高效、有序的管理方法。看板管理的价值:用可视化让复杂工作简单化看板管理的......
  • vue3 带图片的提交按钮
    一、界面元素<el-formv-loading="isLoading":model="editForm"ref="ruleFormRef":rules="rules"label-width="120px"><el-row><el-col:span="12">......
  • 算法网关视频分析网关拍照检测高空抛物检测算法:守护城市安全的“天眼”
    高空抛物,一个看似微不足道的行为,实则隐藏着巨大的安全隐患。随着城市化进程的加快,高层建筑如雨后春笋般拔地而起,高空抛物现象也随之增多,给人们的生活带来了严重的威胁。从烟头、饮料瓶到花盆、垃圾,这些被随意抛掷的物品,一旦从高空落下,其破坏力不容小觑。为了有效预防和减少高空抛......
  • hadoop~搭建HA集群之后不能自动切换namenode
    在搭好HA集群之后,想测试一下集群的高可用性,于是先把active的namenode给停掉:hadoop-daemon.shstopnamenode或者直接kill掉该节点namenode的对应进程也可。但是通过hdfshaadmin-getServiceStatemaster1查看,发现standby的namenode并没有自动切换成active,直到我把之前kill......
  • HTML - 2
    HTML-2HTML5是新一代HTML标准,由万维网联盟W3C完成标准制定在狭义上是最新一代的HTML标准,广义上是指整个前端HTML5优势针对Javascript新增了很多可操作的接口新增了一些语义化标签、全局属性新增了多媒体标签,可以很好地替代flash更加侧重语义化,对于S......
  • Django框架--ORM操作
    1.查询操作1.1filter()用法:用于过滤查询结果,返回符合条件的记录。支持链式调用。示例:queryset=MyModel.objects.filter(field=value)SQL查询示例:SELECT*FROMMyModelWHEREfield=value;性能特点:根据过滤条件生成SQL查询。支持复杂查询条件,性能取决于查询条件和索引。1......
  • 前端技术学习路线图
    以下Web开发人员学习路线图是来自Githubdeveloper-roadmap项目,目前已经有繁体版翻译developer-roadmap-chinese。主要有三个方向,分别为前端开发、后端开发和运维。图片中不同颜色的意义:黄色:推荐;灰色:尽可能学习;橙色:任选其一。可以看到,作为Web开发者,不管从事什么职位,下......
  • 10 EXcel表格数据透视表介绍
     1.数据透视表的基本概念1.1什么是数据透视表数据透视表是Excel中用于快速汇总、分析和呈现数据的强大工具。通过简单的拖拽操作,可以轻松对数据进行分类汇总、筛选、分组等操作,适用于大数据量的分析。核心特点:数据动态汇总:可以自动更新结果。强大的数据可视化:支持生成......