单下划线\_xxx是约定性私有,仅提示开发者勿随意访问,运行时无限制;双下划线\_\_xxx触发名称改写以避免子类误覆盖;\_\_xxx\_\_是魔法方法,与访问控制无关,专供解释器调用。

单下划线 _xxx 是约定,不是限制
它只是告诉其他开发者“这个别乱碰”,Python 解释器完全不管。你写 obj._attr 一样能读、能改、能删,不会报错。这种设计源于 Python 的哲学:“We are all consenting adults.”——它信任你有判断力,而不是靠语法锁死。
常见错误现象:有人以为加了 _ 就安全了,结果在测试或子类里直接访问 _attr,后续重构时才发现依赖了本该“内部”的实现细节。
- 模块导入时,
from module import *不会导入以_开头的名称(除非显式定义__all__) - IDE 和 linter(如 pylint)会把它标为“protected-access”,但运行时毫无影响
- 子类可以自由继承并覆盖
_attr或_method(),没有冲突风险
双下划线 __xxx 触发名称改写(name mangling)
这不是访问控制,而是防误覆盖机制。Python 在编译阶段就把 __attr 改成 _ClassName__attr,目的是避免子类不小心重名覆盖父类的内部属性或方法。
典型错误现象:在子类里定义了同名的 __value,却发现父类逻辑异常——其实两个属性根本不是同一个,因为各自被改写成了 _Parent__value 和 _Child__value。
-
print(obj.__attr)会报AttributeError,但print(obj._ClassName__attr)能正常输出 - 改写只发生在类定义体内,且仅针对以双下划线开头、**不以双下划线结尾**的标识符(如
__init__不改写) - 子类无法通过
self.__attr访问父类的__attr,必须用改写后的名字,或者走父类提供的公开接口
__xxx__ 是魔法方法,和访问控制无关
像 __init__、__str__ 这种前后都带双下划线的,是 Python 预留的特殊钩子,用于触发特定行为。它们不是私有,也不做名称改写,更不是“更高级的私有”。
容易踩的坑:有人看到 __xxx__ 就以为比 __xxx 更私有,甚至自己定义 __my_method__——这违反 PEP 8,还可能和未来 Python 版本新增的魔法方法冲突。
- 自定义名称尽量避开
__xxx__形式,哪怕看起来很“酷” -
__xxx__的存在是为了让解释器调用,不是给你手动调用的(比如别写obj.__str__(),该用str(obj)) - 名称改写机制对
__xxx__完全不生效,obj.__len__就是obj.__len__
什么时候该用哪个?
选错符号不是语法错误,但会让协作和维护变困难。关键不是“能不能访问”,而是“别人是否该知道它的存在”。
- 想表达“这是内部实现,别依赖” → 用
_attr(比如_cache、_validate()) - 想防止子类意外覆盖某个关键内部状态 → 用
__attr(比如__version、__lock) - 需要定义构造、比较、序列化等行为 → 只用标准
__xxx__(如__eq__、__getitem__),别造新词 - 变量名和关键字冲突(比如要存一个
class字段)→ 用class_,不是_class或__class
最常被忽略的一点:双下划线带来的名称改写,在调试、序列化(如 pickle)、反射(getattr)时都会暴露真实名字,硬编码 _Class__attr 会让代码脆弱。真要隔离,靠封装+文档+接口,而不是靠下划线数量。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











