You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现可装饰@property且接收参数的Python字段校验装饰器

问题根源

装饰器逻辑不触发由两个核心问题导致:

  • 装饰器堆叠顺序错误:当前写法将@required_fields放在@property外层,此时传入装饰器的参数f是property描述符对象,不是类中定义的getter方法。最终返回的wrapper是普通函数,会直接替换掉原本的property,实例访问属性时不会触发预设的校验逻辑。
  • 第一版装饰器存在逻辑缺陷:校验完成后直接return f,没有调用被装饰的目标方法,即使顺序正确也无法返回正确的属性计算结果。
解决方案

方案1:调整装饰器堆叠顺序(改动最小)

你写的第二版装饰器核心逻辑已经正确,只需要调整装饰器的应用顺序:将@property放在最外层,@required_fields放在靠近被装饰方法的位置。装饰器执行遵循「从近到远」的规则,会先给原始getter方法加上校验逻辑,再把包装后的方法转成property属性,访问时就会正常触发校验。

修正后的完整可运行代码:

import functools
import dataclasses
import typing
import datetime

M = float

def required_fields(required_fields):
    def _required_fields(f):
        @functools.wraps(f)
        def wrapper(self, *args, **kwargs):
            for field in required_fields:
                if getattr(self, field) is None:
                    raise ValueError(f"Missing {field}")
            return f(self, *args, **kwargs)
        return wrapper
    return _required_fields

@dataclasses.dataclass
class LoanCalculator:
    _amount: typing.Optional[M] = None
    _interest_rate: typing.Optional[M] = None
    _years: typing.Optional[M] = None
    _balance: typing.Optional[M] = None
    _payment_day: typing.Optional[int] = None
    _start_date: typing.Optional[datetime.date] = None

    class MissingDataError(Exception):
        pass

    # 注意装饰器顺序:@property 在外层,@required_fields 在内层
    @property
    @required_fields(['_interest_rate'])
    def monthly_interest_rate(self):
        return self._interest_rate / 12

测试效果:

calc = LoanCalculator()
# 未设置_interest_rate时,抛出 ValueError: Missing _interest_rate
print(calc.monthly_interest_rate)

calc._interest_rate = 0.12
# 正常返回计算结果 0.01
print(calc.monthly_interest_rate)

方案2:编写兼容property的装饰器(无需记忆顺序)

如果不想每次手动调整装饰器顺序,可以修改装饰器逻辑,自动识别传入的对象是否为property实例,提取内部的getter方法做包装后再重新生成property返回。这种写法不管装饰器顺序如何都能正常运行:

def required_fields(required_fields):
    def _required_fields(f):
        # 识别被装饰对象是property的情况
        if isinstance(f, property):
            original_getter = f.fget
            @functools.wraps(original_getter)
            def wrapper(self, *args, **kwargs):
                for field in required_fields:
                    if getattr(self, field) is None:
                        raise ValueError(f"Missing {field}")
                return original_getter(self, *args, **kwargs)
            # 保留原property的setter、deleter和文档
            return property(wrapper, f.fset, f.fdel, f.__doc__)
        
        # 普通实例方法的处理逻辑
        @functools.wraps(f)
        def wrapper(self, *args, **kwargs):
            for field in required_fields:
                if getattr(self, field) is None:
                    raise ValueError(f"Missing {field}")
            return f(self, *args, **kwargs)
        return wrapper
    return _required_fields

内容的提问来源于stack exchange,提问作者Milano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 09:18:19