python类中直接赋值的“常量”无法阻止修改,因无真正常量机制;需用元类重写__setattr__拦截类属性赋值,并通过显式声明_constants集合来识别和保护常量。

为什么直接在类里赋值的“常量”根本拦不住修改
Python 没有真正的常量语法,CLASS_CONSTANT = 42 这种写法只是约定俗成;实例或类本身都能直接覆写:MyClass.CLASS_CONSTANT = 99 或 obj.CLASS_CONSTANT = 100 都不会报错。靠文档或命名规范(比如全大写)完全无效——运行时毫无约束力。
想真正拦截修改,必须介入属性访问/赋值机制。最底层、最可控的方式是自定义元类,在 __setattr__ 被调用前就识别出对类属性的非法写入。
用元类在 __setattr__ 中拦截类属性修改
关键不是重写实例的 __setattr__,而是让元类接管类对象自身的属性设置行为。当执行 MyClass.SOME_CONST = ... 时,实际调用的是元类的 __setattr__。
实操要点:
- 继承
type定义元类,重写其__setattr__ - 在元类
__setattr__中判断:被设置的是否为当前类(而非子类)、属性名是否在预设常量列表中、新值是否与原值不同 - 只对类对象本身的属性赋值做拦截,不干扰实例属性(实例的
__setattr__由类自己控制) - 使用
object.__setattr__(cls, name, value)绕过自身逻辑完成合法赋值(比如第一次初始化)
示例元类:
class ConstantMeta(type):
def __setattr__(cls, name, value):
# 只检查本类(非子类),且属性已存在且标记为常量
if (name in getattr(cls, '_CONSTANTS', set()) and
hasattr(cls, name) and
getattr(cls, name) != value):
raise TypeError(f"Cannot reassign constant {name!r} on {cls.__name__}")
super().__setattr__(name, value)
如何声明和识别哪些是常量
不能靠命名规则自动识别(比如全大写),因为容易误判,也不支持动态控制。更可靠的做法是显式声明。
推荐两种方式:
- 在类定义顶部用类变量
_CONSTANTS = {'PI', 'MAX_RETRY'}显式列出常量名 - 用类装饰器或元类参数(如
class MyClass(metaclass=ConstantMeta, constants=['A', 'B']))——但需在元类__new__中解析,增加复杂度
注意:_CONSTANTS 本身不能是常量(否则元类初始化时就拦住了),应作为普通类属性存在,仅用于标识其他属性。首次赋值仍需允许,所以拦截逻辑里要加 hasattr(cls, name) 判断是否已初始化。
实例属性不受影响,但要注意继承和 __slots__
这个元类只拦截「类对象」的属性赋值,不影响任何实例的 __dict__ 操作。子类默认不继承父类的 _CONSTANTS,除非显式复制或用 __init_subclass__ 透传。
如果类用了 __slots__,元类拦截依然有效,因为 __slots__ 约束的是实例属性,而常量是类属性,存储在类字典里。
最容易被忽略的一点:元类的 __setattr__ 对类方法、类属性(@classmethod、@staticmethod)同样生效。如果你把方法名也写进了 _CONSTANTS,后续重绑定就会失败——这不是 bug,是设计使然。常量列表必须严格限定为纯数据属性名。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











