Python装饰器中自定义功能

本文探讨了Python中装饰器的灵活性与setattr、getattr的运用,展示了如何通过装饰器为函数添加可配置逻辑,实现根据不同环境调整接口逻辑,以及如何为装饰器定义属性,使其在面对需求变化时更具机动性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

场景

如果我们面临一个变动很频繁的业务,以后也许需要加上一些当前想不到的逻辑,这个时候就没有办法仅仅通过参数来控制了。那么有没有办法不仅仅是传入参数,而是可以给装饰器添加不同的逻辑呢?

setattr和getattr操作

这两个方法一个是设置属性一个是获取属性

class A:
    def __init__(self):
        self.name = 'hello'

之后,我们可以使用getattr方法去获得它的name属性

a = A()
getattr(a, 'name')

有get自然就有set,我们也可以通过setattr为它附上新的属性。第二个参数是新增的属性名称,第三个参数是属性的值。

setattr(a, 'age', 18)

当我们去执行a.age的时候,就会获得18。这里要注意的是,我们只是单纯地为a这个实例创建了新的属性,并没有更改A这个类中的定义。所以其他A这个类的实例并不会受到影响,另外如果我们将多个值赋值给了同一个属性名会发生覆盖,也就是后面的覆盖前面的。

属性这个词在Python中的定义是比较宽泛的,除了变量可以称作是属性,函数也一样可以作为属性。也就是说我们除了可以添加一个变量之外,也可以添加一个函数

我们来看个例子:

def print_log():
    print('This is a log')
复制代码

这是一个简单的demo方法,我们通过setattr将它赋值给实例a,那么我们就可以在实例a中调用它了。

不仅仅如此,类也一样可以通过setattr方法设置。

理解了setattr和getattr的用法之后,我们不禁有一个问题,我们通过.操作不香吗,为什么还要搞一个setattr和getattr出来呢?

如果我们自己写代码写着玩,当然是用.操作更方便,但如果是实际的开发场景。很有可能我们需要添加的属性的名称是个变量,而不是写死的,也就是说是可配置的。这个时候就不能通过.了,我们考虑问题的时候不能仅仅从功能入手,也需要思考一下它的使用场景。

为装饰器定义属性

setattr我们都已经熟悉了,接下来回到正题。Python当中一切都是对象,同样函数也是对象。既然函数也是对象,那么我们就可以给函数也设置属性。装饰器的本质就是函数,所以我们可以给装饰器内包装的函数也设置属性,为了方便大家理解,我先不用setattr,让大家看看单纯的带属性的装饰器是什么样的。

def decorate(func):
    logmsg = func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        print(logmsg)
        return func(*args, **kwargs)

    def set_message(newmsg):
        nonlocal logmsg
        logmsg = newmsg
        
    wrapper.set_message = set_message
    return wrapper

如果我们把set_message这个方法拿掉的话,它就是一个普普通通的装饰器。set_message方法当中,我们使用nonlocal关键字修改了logmsg这个变量的值,而这个值会在装饰器的包装函数当中用到。也就是说我们通过调用set_message方法,可以修改这个装饰器的运行结果和逻辑。

这里,我们没用装饰器,而是简单地使用了.关键字来对它进行了赋值。还是和之前说的一样,这样当然是可以的,但是如果我们想要配置这个name就做不到了。最常见的场景就是区分线上和测试环境,一种做法是在接口的名字之前加上一个标识,比如线上是online,测试环境是test或者是dev。通过这种方法区分不同环境的逻辑。

所以比较好的方法是将这个逻辑也写成一个装饰器,将被包装的方法作为参数传入。如果你看明白了上一篇文章,熟悉装饰器传参的话,这段代码对你来说应该很简单。

def attach(obj):
    @wraps(obj)
    def wrapper(func):
        setattr(obj, func.__name__, func)
        return func
    return wrapper

有了attach这个装饰器之后,我们只需要给set_message这个方法加上注解,将被包装的函数作为参数传入即可。

    @attach(wrapper)
    def set_message(newmsg):
        nonlocal logmsg
        logmsg = newmsg

如果只是想要实现功能,而不追求规范的话,可以使用partial来简化代码,减少它的层次结构:

def attach(obj, func=None):
    if func is None:
        return partial(attach_wrapper, obj)
    setattr(obj, func.__name__, func)
    return func

这样写也是可以work的,只要熟悉partial的用法,应该也不难理解。

让函数为所欲为

如果你是一个程序员,你面临一个变动很频繁的业务,你无法预知之后的需求情况,想要代码有足够大的机动余地,这个时候可以利用强大的setattr给程序留一个“后门”,方便后面临时修改。

具体的做法其实很简单,我们在装饰器当中定义一个dict,用来存储自定义的函数。再实现一个set_func方法将自定义的函数存储进这个dict当中,只有就可以通过参数,在不修改装饰器的情况下自由变更装饰器内的逻辑了。

我们来看代码:

def decorate(func):
    func_dict = {}

    @wraps(func)
    def wrapper(*args, **kwargs):
        # 通过key来选择应该调用哪一个函数作为装饰器的逻辑
        if kwargs.get('key') is not None:
            func_dict[kwargs['key']](*args, **kwargs)
        return func(*args, **kwargs)
        
    # 将函数名和函数作为参数传入,存储在dict中
    @attach(wrapper)
    def set_func(func_name, func):
        nonlocal func_dict
        func_dict[func_name] = func

    return wrapper

 

我们再来看一个使用的例子:

def test(*args, **kw):
    print('test')
add.set_func('test', test)
add(3, 4, key='test')

这样,我们就把test方法中的逻辑放入了装饰器当中,只有我们需要,我们还可以写出其他的方法,来自定义我们对装饰器的需求,而又不需要修改装饰器内部的逻辑。不仅如此,我们还可以在主体函数的前后都加上这样的逻辑,真的可以说是为所欲为了。

当然一般情况下我们用不到这样的骚操作,但是能够写出来或者说看懂这样的功能,那就说明关于装饰器的理解已经算是入门了。

<think>我们正在处理用户的问题:如何在Python装饰器中接收和处理自定义异常? 根据引用[3],我们有一个使用装饰器处理异常的示例,但它处理的是内置异常(TypeError, ValueError等)。用户现在希望处理自定义异常。 步骤: 1. 首先,我们需要定义一个自定义异常类。 2. 然后,创建一个装饰器,它可以捕获自定义异常(也可以同时捕获其他异常)。 3. 在装饰器内部,我们可以根据捕获到的异常类型进行特定的处理。 示例代码结构: - 定义自定义异常类,例如`MyCustomError`。 - 创建装饰器函数,例如`handle_custom_exceptions`,它可以捕获自定义异常。 - 使用装饰器装饰一个会抛出自定义异常的函数。 注意:引用[2]展示了如何创建带参数的装饰器,但用户目前没有要求带参数,所以我们先实现一个基本的。 另外,引用[3]中的装饰器可以处理多种异常,我们可以扩展它,加入自定义异常。 我们将编写一个装饰器,它可以捕获特定的自定义异常,并打印出相应的消息,同时也可以捕获其他异常(可选)。 由于用户没有指定具体需求,我们假设用户希望装饰器能够处理一个或多个自定义异常,并允许用户自定义处理方式(这里我们简单打印,但可以扩展)。 为了清晰,我们先实现一个专门处理自定义异常的装饰器,然后再展示一个更通用的版本(处理自定义异常和内置异常)。 示例1:专门处理自定义异常 示例2:处理多个异常,包括自定义异常 最后,根据用户要求,生成相关问题。 注意:用户要求使用中文回答,行内公式用$...$,独立公式用$$...$$(但本问题不涉及数学公式,所以可以忽略),以及生成相关问题。</think>### Python装饰器处理自定义异常 在Python中,通过装饰器处理自定义异常需要三个关键步骤: #### 1. **定义自定义异常类** ```python class CustomException(Exception): """自定义异常基类""" def __init__(self, message="发生自定义异常"): self.message = message super().__init__(self.message) class SpecificError(CustomException): """特定业务异常""" pass ``` #### 2. **创建异常处理装饰器** ```python def handle_custom_exceptions(*exceptions): """处理自定义异常的装饰器工厂""" def decorator(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except exceptions as e: # 捕获指定异常 print(f"捕获自定义异常: {type(e).__name__} - {e}") # 这里可扩展为日志记录/报警等操作 return None # 或执行恢复逻辑 except Exception as e: # 其他异常处理 print(f"系统异常: {type(e).__name__}") raise # 重新抛出非自定义异常 return wrapper return decorator ``` #### 3. **应用装饰器** ```python @handle_custom_exceptions(SpecificError, CustomException) # 注册要处理的异常类型 def risky_operation(value): if value < 0: raise SpecificError("数值不能为负") if value > 100: raise CustomException("数值超限") return value * 2 # 测试 print(risky_operation(50)) # 正常: 100 risky_operation(-10) # 输出: 捕获自定义异常: SpecificError - 数值不能为负 risky_operation(200) # 输出: 捕获自定义异常: CustomException - 数值超限 ``` #### 关键特性: 1. **动态异常注册** 通过`*exceptions`参数可注册多个异常类型,实现灵活的异常处理策略[^2] 2. **异常隔离机制** 使用`except exceptions as e`精确捕获目标异常,避免过度捕获[^4] 3. **异常传播控制** 非自定义异常通过`raise`重新抛出,保持原始堆栈信息[^3] 4. **扩展性** 可在装饰器内添加日志记录、重试机制等增强功能: ```python except exceptions as e: logger.error(f"业务异常: {e}") if isinstance(e, SpecificError): return fallback_value # 执行恢复逻辑 ``` 实际应用中,建议将异常处理与业务逻辑解耦,这种装饰器模式在API接口验证、事务管理和输入校验等场景特别有效[^1]。 --- ### 相关问题 1. 如何在装饰器中实现异常重试机制? 2. 自定义异常与标准异常在继承结构上有何最佳实践? 3. 如何通过装饰器实现跨函数的异常上下文传递? 4. 在异步函数中处理自定义异常需要注意哪些特殊问题? 5. 如何避免装饰器导致的异常堆栈信息丢失? [^1]: 异常处理的核心在于分离错误处理与业务逻辑 [^2]: 参数化装饰器可动态配置异常处理行为 [^3]: 保持异常传播链完整对调试至关重要 [^4]: 精确捕获避免掩盖潜在的系统级错误
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值