只读描述符是保障支付配置不可篡改的底层机制,通过未实现__set__方法在解释器层拦截所有赋值操作,配合__slots__、实例冻结和typing.final实现运行时、解释器层与开发阶段三重防护。

只读描述符是保障支付配置不可篡改的底层机制,它比单纯用@property更可控、更可靠,尤其适合迭代流(streaming)式支付场景中频繁读取但绝不允许运行时修改的配置项,比如交易超时阈值、默认币种、风控白名单URL、签名密钥指纹等。
为什么普通私有属性或@property不够用
普通私有属性(如self._timeout)仅靠命名约定无法阻止赋值;@property虽可省略setter,但依然能通过object.__dict__或setattr()绕过——这对支付系统是致命漏洞。而描述符通过__set__方法在属性赋值入口统一拦截,只要不实现该方法,任何尝试写入都会触发AttributeError,且该拦截发生在解释器最底层,无法被反射或动态赋值绕过。
构建安全的只读描述符类
定义一个显式拒绝写入的描述符:
- 继承
object,实现__get__返回存储值 - 不实现
__set__和__delete__,或显式抛出AttributeError - 值应初始化在实例字典或描述符自身(推荐前者,避免多实例共享)
示例:
class ReadOnlyDesc:def __init__(self, name):
self.name = f"_ro_{name}"
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.name)
def __set__(self, obj, value):
raise AttributeError(f"Cannot modify read-only attribute '{self.name[4:]}'")
在支付配置类中集成并初始化
关键点在于:只读值必须在__init__中一次性写入实例字典,之后所有访问均走描述符__get__,杜绝二次赋值可能:
- 构造函数内调用
setattr(self, "_ro_timeout", timeout)完成初始化 - 类属性声明为
timeout = ReadOnlyDesc("timeout") - 禁止在其他方法中再次调用
setattr或直接操作__dict__
这样即使攻击者执行payconf.timeout = 300,也会立即报错,不会静默失败。
配合冻结与类型检查加固
单靠描述符还不够。建议叠加以下防护:
- 使用
__slots__ = ("_ro_timeout", ...)禁用动态属性添加,防止新增可写字段 - 在
__init__末尾调用object.__setattr__(self, "_frozen", True),并在__set__中校验_frozen标志 - 配合
typing.Final和mypy做静态检查,提前捕获非法赋值语句
这三层控制覆盖了运行时、解释器层和开发阶段,确保迭代流支付配置从加载到销毁全程不可篡改。










