__attr能被外部访问的根本原因是python仅在编译期自动重命名为_classname__attr,而非运行时拦截;名称改写仅作用于类定义内静态声明的双下划线标识符,不适用于魔法方法、动态属性或子类同名属性。

__attr 能被外部访问,根本原因不是“绕过保护”,而是 Python 根本没做运行时拦截——它只是在类定义阶段把 __attr 自动重命名为 _ClassName__attr,仅此而已。
名称改写(name mangling)不是加密,是字符串拼接
Python 解释器在解析类体时,遇到以双下划线开头、且不以双下划线结尾的标识符(如 __data),会机械地把它替换成 _类名__data。这个过程发生在编译期,不涉及权限检查、不调用任何钩子函数。
-
__init__、__str__这类前后都带双下划线的魔法方法,不会被改写 - 动态添加的属性,比如
obj.__x = 1,也不会被改写,因为 name mangling 只作用于类定义内部的静态声明 - 子类中同名的
__attr会被各自改写为_Parent__attr和_Child__attr,互不干扰
怎么确认改写后的实际名字?别猜,查 __dict__ 或 dir()
硬编码 _MyClass__data 风险很高:类名一改就失效;移到父类后路径错乱;IDE 和类型检查器(如 mypy)通常报 has no attribute。
- 最可靠方式:
print(obj.__dict__)—— 直接看到键名,例如{'_User__balance': 999} - 辅助验证:
print([x for x in dir(obj) if 'balance' in x]) - 注意:
help(obj)和大多数 IDE 显示的是原始名__balance,这是展示层伪装,不是真实存储名
强行访问 _Cls__attr 的真实代价
这不是技术能不能的问题,而是工程要不要的问题。一旦你写了 user._User__balance += 100,就等于把实现细节钉死成接口。
- 后续把
__balance改成_balance或加验证逻辑,这行代码就会静默出错或跳过校验 - Pydantic / dataclass / SQLAlchemy 等库对双下划线字段有特殊处理,直接读改写名可能绕过验证、触发未定义行为
- 单元测试里大量依赖
_Cls__x,等于把测试和内存布局耦合,一次重构就要批量修断言
@property、提供 _for_test_only() 方法,或者干脆把 __attr 改成 _attr——Python 的私有机制,本质是提醒你:这里不是出口,是内墙。墙上有门,但门钥匙不在墙上刻着。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











