Python 的单下划线 _x 和双下划线 __x 有什么区别?名称改写是什么?
简化版
**Python 没有真正的「私有」访问控制,而是用「下划线命名约定」表达访问意图,其中双下划线前缀会触发「名称改写(name mangling)」——这是唯一有语言机制支持的:① 单下划线 _x(「保护」约定)——只是「约定俗成的提示:这是内部使用的,请别在外部访问」,但语言不强制,你照样能访问 obj._x;② 双下划线 __x(「名称改写」)——Python 会在类内部把 __x 自动改写成 _类名__x,主要目的是「避免子类意外覆盖父类的属性」(不是为了「私有保护」)。关键:① _x 纯约定(提示别碰、from module import * 不会导入);② __x 会名称改写(class A: self.__x → 实际存成 self._A__x),子类的 __x 变成 _子类__x,所以父子类的 __x 互不冲突;③ 双下划线不是「私有」——你仍能通过改写后的名字 obj._A__x 访问(只是不方便);④ 首尾双下划线 __x__(如 __init__)是「魔术方法」,不触发名称改写。核心记忆:_x 是「内部使用」的约定(不强制)、__x 触发名称改写成 _类名__x(防子类覆盖、非私有)、__x__ 是魔术方法(不改写)。
详细版
三种下划线命名:
| 命名 | 含义 | 机制 |
|---|---|---|
_x | 「内部/保护」约定 | 纯约定(import * 不导入) |
__x | 名称改写(防子类覆盖) | 改写成 _类名__x |
__x__ | 魔术方法/特殊属性 | 不改写(Python 内部用) |
x_ | 避免和关键字冲突 | 如 class_、type_ |
class Base:
def __init__(self):
self.public = 1 # 公开
self._protected = 2 # 约定:内部使用(提示别碰,但能访问)
self.__private = 3 # 名称改写 → 实际存成 _Base__private
b = Base()
print(b.public) # 1
print(b._protected) # 2(约定"别碰",但语言不阻止)
# print(b.__private) # ✗ AttributeError(因为实际叫 _Base__private)
print(b._Base__private) # 3(改写后的真名,仍能访问——不是真私有)
print(b.__dict__) # {'public':1, '_protected':2, '_Base__private':3}
# 名称改写的用途:防止子类意外覆盖
class Parent:
def __init__(self):
self.__value = "parent" # 改写成 _Parent__value
def get_parent_value(self):
return self.__value # 类内部访问,自动用 _Parent__value
class Child(Parent):
def __init__(self):
super().__init__()
self.__value = "child" # 改写成 _Child__value(不覆盖父类的!)
def get_child_value(self):
return self.__value # 用 _Child__value
c = Child()
print(c.get_parent_value()) # "parent"(父类的 _Parent__value,没被覆盖)
print(c.get_child_value()) # "child"(子类的 _Child__value)
print(c.__dict__) # {'_Parent__value':'parent', '_Child__value':'child'}
# 魔术方法 __x__ 不改写
class C:
def __init__(self): ... # __init__ 不会变成 _C__init__
def __custom__(self): ... # 首尾双下划线也不改写
# 单下划线在 import * 时的作用
# 模块里 _helper 不会被 from module import * 导入(约定的私有)
# 关键字冲突用尾下划线
def func(class_, type_): # class、type 是关键字/内置,加尾下划线
...
⚠️ 核心澄清:Python 里「双下划线
__x」经常被误解为「私有」,但它的真正目的是「名称改写来避免子类意外覆盖父类属性」,而不是「访问保护」——因为你仍然能通过改写后的名字obj._类名__x访问它,只是不方便而已;Python 的哲学是「我们都是成年人(we’re all consenting adults)」,靠约定而非强制。三个层次要分清:①_x(单下划线)纯粹是约定——「这是内部实现,外部请别依赖它」,语言完全不阻止你访问,唯一的语言效果是from module import *不会导入以_开头的名字;②__x(双下划线、无尾部下划线)触发名称改写——编译器在类定义内部把所有self.__x改写成self._类名__x(__xinclass Base→_Base__x),这样父类和子类各自的__x变成不同的真名(_Parent__xvs_Child__x),从而「子类定义__x不会意外覆盖父类的__x」——这才是名称改写的设计初衷(防止继承时的命名冲突,尤其是父类方法内部用了self.__x、不希望被子类的同名属性搞乱);③__x__(首尾都双下划线)不改写——这是「魔术方法/dunder」的命名空间(__init__、__str__),Python 内部保留、不做名称改写。实践建议:想表达「内部使用」用单下划线_x(够了);只在「确实需要避免子类命名冲突」时才用双下划线__x(较少见);别把__x当私有保护用(它不是)。
完整版教学
一、Python 没有真正的私有
先理解 Python 的访问控制哲学:
Python 没有 private/protected/public 关键字:
不像 Java/C++ 有强制的访问修饰符
→ Python 靠"命名约定"表达访问意图
哲学:"We're all consenting adults here"
(我们都是成年人)
→ 相信程序员会遵守约定,不强制阻止
→ 不搞"语言级的访问墙",用约定沟通意图
三种约定(下划线):
public:正常命名(name、value)→ 公开 API
_x:单下划线 → "内部使用,请别碰"(约定,不强制)
__x:双下划线 → 名称改写(防子类覆盖,不是私有)
__x__:首尾双下划线 → 魔术方法(Python 保留)
为什么不做强制私有:
① 灵活——调试、测试、元编程需要访问"内部"
② 信任——约定足够,成年人会守约
③ 简单——不增加语言复杂度
对比 Java:
Java:private 强制(外部真的访问不了)
Python:_x/__x 是约定/改写(外部仍能访问,只是提示/不方便)
关键认知:
Python 的"私有"是"文化"不是"机制"
→ _x 提示别碰、__x 防冲突,都不是"访问墙"
所以 Python 无真正私有(靠命名约定):_x 提示内部、__x 名称改写、都不强制阻止访问
Python 没有 private/protected/public 关键字:不像 Java/C++ 有强制的访问修饰符、靠「命名约定」表达访问意图。哲学:「We’re all consenting adults here」(我们都是成年人):相信程序员会遵守约定、不强制阻止、用约定沟通意图。三种约定(下划线):public(正常命名、公开 API)、_x(单下划线、内部使用请别碰、约定不强制)、__x(双下划线、名称改写防子类覆盖、不是私有)、__x__(首尾双下划线、魔术方法 Python 保留)。为什么不做强制私有:① 灵活(调试/测试/元编程需要访问内部)、② 信任(约定足够)、③ 简单(不增加语言复杂度)。对比 Java:Java 的 private 强制(外部真访问不了)、Python 的 _x/__x 是约定/改写(外部仍能访问)。理解「Python 无真正私有(无 private 关键字、靠命名约定);哲学都是成年人(信任约定不强制);_x 提示内部、__x 名称改写、__x__魔术方法;Java private 强制、Python 约定/改写仍能访问」,就理解了 Python 的访问控制哲学。
二、单下划线 _x
理解 _x 的约定含义:
单下划线前缀 _x:约定"内部使用"
含义:"这是内部实现细节,外部请别直接用"
→ 纯约定,语言不阻止访问
语言层面的唯一效果:
from module import * 不会导入 _ 开头的名字
# module.py: _helper(下划线开头)
# from module import * → _helper 不被导入(约定的"模块私有")
(但 import module 后 module._helper 仍能访问)
用途:
① 标记"内部方法/属性"(不是公开 API)
class C:
def _internal(self): ... # 内部辅助方法
self._cache = {} # 内部状态
② 提示 API 使用者"别依赖这个"
③ 单独一个 _ 表示"我不关心这个值"
for _ in range(3): ... # 循环变量不用
a, _, c = (1, 2, 3) # 忽略中间值
不改变属性的存储:
self._x = 1 → 存成 _x(名字不变,只是加了下划线)
obj._x → 正常访问(能访问,只是"约定别碰")
单下划线做变量名(特殊):
_ 在交互式解释器里是"上一个表达式的结果"
_ 常用于国际化 gettext(_("text"))
所以_x 是"内部使用"约定(不强制、能访问),唯一效果 import *不导入;单个_表示忽略
单下划线前缀 _x:约定「内部使用」——含义「这是内部实现细节、外部请别直接用」(纯约定、语言不阻止访问)。语言层面的唯一效果:from module import * 不会导入 _ 开头的名字(约定的「模块私有」、但 import module 后 module._helper 仍能访问)。用途:① 标记内部方法/属性(def _internal、self._cache)、② 提示 API 使用者别依赖、③ 单独一个 _ 表示不关心这个值(for _ in range(3)、a, _, c = (1,2,3))。不改变属性的存储:self._x = 1 存成 _x(名字不变、只是加了下划线、正常访问)。单下划线做变量名(特殊):_ 在交互式解释器是「上一个表达式的结果」、常用于国际化 gettext(_("text"))。理解「_x 是内部使用约定(不强制、能访问);唯一语言效果 from module import *不导入 _开头;用途标记内部/提示别依赖/单个_忽略值;不改变存储(名字不变)」,就掌握了单下划线。
三、双下划线 __x 与名称改写
理解名称改写机制:
双下划线前缀 __x(无尾部下划线):触发"名称改写"
编译器在类内部把 __x 改写成 _类名__x
机制:
class Base:
def __init__(self):
self.__x = 1 # 在 Base 里,__x 改写成 _Base__x
→ 实际存成 self._Base__x
b = Base()
b.__dict__ # {'_Base__x': 1} ← 真名是 _Base__x
b.__x # AttributeError(没有叫 __x 的属性)
b._Base__x # 1(用改写后的真名能访问)
改写规则:
在 class ClassName 内部,__名字(≥2 前导下划线、≤1 尾部下划线)
→ 改写成 _ClassName__名字
只在"类定义内部"改写(类外部写 __x 不改写)
真正目的:防止子类意外覆盖父类属性(不是私有!)
class Parent:
def __init__(self): self.__x = "p" # _Parent__x
def show(self): return self.__x # 类内部访问 _Parent__x
class Child(Parent):
def __init__(self):
super().__init__()
self.__x = "c" # _Child__x(不同!不覆盖)
→ Parent 的 _Parent__x 和 Child 的 _Child__x 互不干扰
→ 父类方法用 self.__x 时永远拿到 _Parent__x(子类改不了)
为什么需要(场景):
父类的方法内部用了 self.__helper
→ 不希望子类无意中定义同名 __helper 把它搞乱
→ 名称改写让它们"隔离"(各自 _父类__helper / _子类__helper)
不是私有保护:
b._Base__x 仍能访问 → 只是"不方便"、不是"访问不了"
所以__x 触发名称改写(改成_类名__x),目的防子类覆盖(非私有),仍能用_类名__x 访问
双下划线前缀 __x(无尾部下划线):触发「名称改写」——编译器在类内部把 __x 改写成 _类名__x。机制:class Base: self.__x = 1 → 实际存成 self._Base__x(b.__dict__ 是 {'_Base__x': 1}、b.__x AttributeError、b._Base__x 能访问)。改写规则:在 class ClassName 内部、__名字(≥2 前导下划线、≤1 尾部下划线)改写成 _ClassName__名字(只在类定义内部改写、类外部写 __x 不改写)。真正目的:防止子类意外覆盖父类属性(不是私有!):Parent 的 __x → _Parent__x、Child 的 __x → _Child__x(互不干扰、父类方法用 self.__x 永远拿到 _Parent__x、子类改不了)。为什么需要:父类的方法内部用了 self.__helper、不希望子类无意中定义同名 __helper 把它搞乱(名称改写让它们隔离)。不是私有保护:b._Base__x 仍能访问(只是不方便)。理解「__x 触发名称改写(类内部改成_类名__x、__dict__里真名是_Base__x);目的防子类覆盖(父子类__x 变成_Parent__x/_Child__x 互不干扰),不是私有;仍能用_类名__x 访问;只在类定义内部改写」,就掌握了名称改写。
四、首尾双下划线 x
理解魔术方法的命名:
首尾双下划线 __x__(前后都双下划线):魔术方法/特殊属性
不触发名称改写!
例子:
__init__、__str__、__repr__、__eq__、__len__(魔术方法)
__name__、__doc__、__dict__、__class__(特殊属性)
__all__、__version__(模块级约定)
为什么不改写:
__x__ 是"Python 保留的、有特殊含义的"命名空间
→ 由 Python 解释器识别和调用(如 __init__ 实例化时调)
→ 如果改写成 _类名__init__,Python 就找不到了
改写的判定(精确):
改写:≥2 前导下划线 + ≤1 尾部下划线
__x → 改写(_类名__x)
__x_ → 改写(1 个尾下划线,仍改写)
不改写:≥2 前导下划线 + ≥2 尾部下划线
__x__ → 不改写(魔术方法)
__ → 不改写(就俩下划线,无中间名)
自定义魔术方法(不推荐):
别自己创造 __myname__(可能和未来 Python 的冲突)
→ __x__ 命名空间"归 Python 所有"
四种下划线命名总结:
_x 内部约定(不强制、import * 不导入)
__x 名称改写(防子类覆盖、_类名__x)
__x__ 魔术方法(Python 保留、不改写)
x_ 避免关键字冲突(class_、type_)
所以__x__(首尾双下划线)是魔术方法(Python 保留、不改写),别自造;改写只对__x(尾≤1)
首尾双下划线 __x__(前后都双下划线):魔术方法/特殊属性——不触发名称改写!例子:__init__/__str__/__eq__(魔术方法)、__name__/__doc__/__dict__(特殊属性)、__all__(模块级约定)。为什么不改写:__x__ 是「Python 保留的、有特殊含义的」命名空间(由解释器识别调用、如 __init__ 实例化时调、改写成 _类名__init__ Python 就找不到了)。改写的判定(精确):改写(≥2 前导下划线 + ≤1 尾部下划线,__x → _类名__x、__x_ 仍改写)、不改写(≥2 前导 + ≥2 尾部,__x__ 不改写、__ 不改写)。自定义魔术方法(不推荐):别自己创造 __myname__(可能和未来 Python 冲突、__x__ 命名空间归 Python 所有)。四种下划线命名总结:_x(内部约定)、__x(名称改写)、__x__(魔术方法)、x_(避免关键字冲突)。理解「x(首尾双下划线)是魔术方法/特殊属性(Python 保留、不改写、解释器识别调用);改写判定:≥2 前导+≤1 尾部才改写(__x/_x)、x__不改写;别自造__myname;四种_x/_x/x/x」,就掌握了魔术方法命名。
五、实践建议与陷阱
理解命名约定的实践和坑:
实践建议:
① 内部使用优先用单下划线 _x:
够表达"内部实现、别碰"了(约定清晰、无副作用)
class C:
def _helper(self): ... # 内部方法
self._state = {} # 内部状态
② 双下划线 __x 慎用(只在真需要防冲突时):
✓ 场景:基类的属性不希望被子类意外覆盖
✗ 别把它当"私有保护"(它不是)
✗ 别到处用 __x(名称改写会带来困惑)
③ 魔术方法 __x__ 别自造:
只实现 Python 已有的(__init__/__str__...)
陷阱1:__x 在类外/子类里访问困难
obj.__x 不行(要 obj._类名__x)
子类想访问父类的 __x 也要用 _父类__x(不方便)
陷阱2:名称改写影响 getattr/setattr
getattr(obj, '__x') # 找不到(真名是 _类名__x)
getattr(obj, '_类名__x') # 才对
陷阱3:以为 __x 是私有、外部真的访问不了
→ 错,obj._类名__x 仍能访问(不是访问墙)
陷阱4:动态属性名和改写
在类内部用字符串动态设 __x 不会改写
setattr(self, '__x', 1) # 存成 '__x'(不改写!和 self.__x 不同)
陷阱5:混淆 _x 和 __x 的目的
_x:提示"内部"(沟通意图)
__x:防"子类命名冲突"(机制隔离)
→ 目的不同,别混
推荐做法:
① 90% 场景用 _x(内部约定,够用)
② 极少数防子类冲突用 __x
③ 别指望任何下划线提供"真私有"
所以实践:内部用_x(够了)、__x 慎用(只防冲突非私有)、__x__别自造;陷阱__x 外部要_类名__x
实践建议:① 内部使用优先用单下划线 _x(够表达内部实现、别碰、约定清晰无副作用);② 双下划线 __x 慎用(只在真需要防冲突时)(场景基类的属性不希望被子类意外覆盖、别当私有保护、别到处用);③ 魔术方法 __x__ 别自造(只实现 Python 已有的)。陷阱:陷阱1:__x 在类外/子类里访问困难(obj.__x 不行、要 obj._类名__x);陷阱2:名称改写影响 getattr/setattr(getattr(obj, '__x') 找不到、要 '_类名__x');陷阱3:以为 __x 是私有外部真访问不了(错、obj._类名__x 仍能访问);陷阱4:动态属性名和改写(setattr(self, '__x', 1) 存成 '__x' 不改写、和 self.__x 不同);陷阱5:混淆 _x 和 __x 的目的(_x 提示内部沟通意图、__x 防子类命名冲突机制隔离)。理解「实践:内部用_x(90%场景够了)、__x 慎用(只防冲突非私有)、__x__别自造;陷阱__x 外部/子类要用_类名__x、getattr 要真名、不是访问墙、setattr 字符串不改写、_x 和__x 目的不同」,就掌握了实践与陷阱。
六、总结与实践
总结下划线命名:
四种下划线命名:
_x 单下划线:内部约定(不强制、import * 不导入)
__x 双下划线:名称改写(改成 _类名__x、防子类覆盖)
__x__ 首尾双下划线:魔术方法(Python 保留、不改写)
x_ 尾下划线:避免关键字冲突(class_、type_)
核心澄清:
Python 没有真正的私有(靠约定,不是强制)
_x 是"提示别碰"、__x 是"防子类冲突"
→ 都不是访问墙(obj._类名__x 仍能访问 __x)
名称改写机制:
在类内部,__x(≤1 尾下划线)→ _类名__x
目的:父子类的 __x 隔离(防子类意外覆盖父类)
非私有:改写后的真名仍可访问
哲学:"We're all consenting adults"
信任约定、不强制阻止
实践建议:
① 内部实现/状态 → 用 _x(约定,够用,90%)
② 极少数防子类命名冲突 → 用 __x
③ 魔术方法 → 只用 Python 已有的(别自造 __x__)
④ 别指望下划线提供真私有
陷阱:
__x 外部/子类访问要用 _类名__x
setattr 字符串名不改写
核心总结:
_x 内部约定(提示别碰)
__x 名称改写 _类名__x(防子类覆盖、非私有)
__x__ 魔术方法(不改写)
Python 无真私有、靠约定
所以_x 内部约定、__x 名称改写(防子类覆盖非私有)、__x__魔术方法;Python 无真私有靠约定
四种下划线命名:_x(单下划线、内部约定、不强制、import * 不导入)、__x(双下划线、名称改写成 _类名__x、防子类覆盖)、__x__(首尾双下划线、魔术方法、Python 保留、不改写)、x_(尾下划线、避免关键字冲突)。核心澄清:Python 没有真正的私有(靠约定不是强制)、_x 是提示别碰、__x 是防子类冲突(都不是访问墙、obj._类名__x 仍能访问)。名称改写机制:类内部 __x(≤1 尾下划线)→ _类名__x、目的父子类的 __x 隔离(防子类意外覆盖)、非私有。实践建议:内部实现/状态用 _x(90%)、极少数防子类冲突用 __x、魔术方法只用已有的、别指望真私有。理解「_x 内部约定(提示别碰)、__x 名称改写_类名__x(防子类覆盖非私有)、x__魔术方法(不改写)、x_避关键字;Python 无真私有靠约定;内部用_x、极少防冲突用__x、别自造__x」,就掌握了总结与实践。
记忆钩子:「Python 没有真正的『私有』访问控制,用『下划线命名约定』表达访问意图,哲学是『We’re all consenting adults(我们都是成年人)』靠约定不强制;★单下划线 _x(『内部/保护』约定):只是『约定俗成的提示——这是内部使用的请别在外部访问』,语言不强制、你照样能 obj._x,唯一语言效果是 from module import *不会导入 _开头的名字;★双下划线 __x(『名称改写 name mangling』):Python 在类内部自动把 self.__x 改写成 self._类名__x(class Base 里 __x→Base__x,dict__里真名是_Base__x),★真正目的是『避免子类意外覆盖父类的属性』——Parent 的__x 变_Parent__x、Child 的__x 变_Child__x 互不冲突,父类方法内部用 self.x 永远拿到_Parent__x(子类改不了),不是为了『私有保护』;★关键:x 不是私有——你仍能通过改写后的名字 obj.Base__x 访问(只是不方便);★首尾双下划线__x(如__init/str)是『魔术方法/特殊属性』,Python 保留、由解释器识别调用、不触发名称改写(改写规则:≥2 前导下划线+≤1 尾部下划线才改写);实践:90%场景想表达『内部使用』用单下划线_x(够了)、只在确实需要避免子类命名冲突时才用双下划线__x、别把__x 当私有保护、别自造__x(命名空间归 Python);另 x_尾下划线用于避免和关键字冲突(class/type)」。
七、常见误区与追问
- 误区:双下划线 __x 是 Python 的「私有」属性,外部访问不了。 不是真正的私有——
__x只是触发「名称改写」,Python 把它改写成_类名__x存储,外部访问obj.__x会失败(因为真名不叫__x),但你仍然可以通过改写后的真名obj._类名__x访问它(只是不方便);Python 没有强制的访问控制、没有 Java 那种「private」墙;__x的目的是「避免子类命名冲突」而非「访问保护」;想表达「私有/内部」用单下划线_x(约定)就够了。 - 误区:单下划线 _x 和双下划线 __x 目的一样,都是表示私有。 目的不同——
_x(单下划线)是「约定」:提示「这是内部实现、外部请别依赖」,语言不阻止访问、纯粹是沟通意图;__x(双下划线)是「机制」:触发名称改写(_类名__x),目的是「避免子类无意中定义同名属性覆盖父类的属性」(父子类的__x被改写成不同的真名、互相隔离);一个是「提示别碰」(约定),一个是「防命名冲突」(隔离),别混为一谈;实践中表达「内部」用_x就够了。 - 误区:init、str 这些也会被名称改写。 不会——首尾都是双下划线的
__x__(如__init__、__str__、__dict__)是「魔术方法/特殊属性」的命名空间,不触发名称改写(Python 需要用固定的名字__init__来识别和调用它们,如果改写成_类名__init__就找不到了);名称改写只作用于「≥2 个前导下划线、且 ≤1 个尾部下划线」的名字(如__x、__x_),__x__(≥2 个尾部下划线)不改写;所以你实现魔术方法时它们的名字保持不变、能被 Python 正常识别。 - 误区:在类里用 setattr(self, ‘__x’, v) 和 self.__x = v 效果一样。 不一样——
self.__x = v是「源代码里的属性名」、会在编译时触发名称改写(存成_类名__x);而setattr(self, '__x', v)传的是「运行时的字符串 ‘__x’」、不会被名称改写(字符串是动态的、改写只发生在编译源代码时),会直接存成名为'__x'的属性;所以这两者存的是不同的属性(_类名__xvs'__x')、self.__x(读取时也改写)读到的是前者、读不到后者;名称改写只作用于「源码中直接写的__x标识符」,不作用于动态的字符串属性名。 - 追问:Python 的单下划线、双下划线命名分别是什么含义? 主要四种:①
_x(单下划线前缀)——「内部使用」的约定,提示「这是内部实现细节、外部请别直接依赖」,语言不强制(obj._x照样能访问),唯一的语言效果是from module import *不会导入以下划线开头的名字;②__x(双下划线前缀、无尾部双下划线)——触发「名称改写」,Python 在类内部把它改写成_类名__x,目的是避免子类意外覆盖父类的同名属性(不是私有保护,仍能通过_类名__x访问);③__x__(首尾都双下划线)——「魔术方法/特殊属性」(如__init__、__str__、__dict__),Python 保留、由解释器识别和调用、不触发名称改写;④x_(尾部单下划线)——用于避免和 Python 关键字或内置名冲突(如class_、type_、id_);另外单独一个_常表示「忽略这个值」(for _ in range(3)、a, _, c = ...)。 - 追问:名称改写(name mangling)是怎么工作的,它的真正目的是什么? 工作机制:在类定义的内部,Python 编译器会把所有形如
__名字(至少两个前导下划线、至多一个尾部下划线)的标识符自动改写成_类名__名字——比如在class Base里写self.__x,编译后实际是self._Base__x,存进实例字典的键也是_Base__x;真正目的不是「私有保护」而是「避免子类意外覆盖父类的属性」:考虑一个父类 Parent 的方法内部用了self.__helper,如果子类 Child 无意中也定义了self.__helper,在没有名称改写的情况下子类的会覆盖父类的、可能破坏父类方法的行为;有了名称改写,Parent 里的__helper变成_Parent__helper、Child 里的变成_Child__helper,两者是不同的属性、互不干扰,父类方法用self.__helper时永远访问自己的_Parent__helper(子类改不了它);所以名称改写是「为继承场景做的命名隔离」;它不提供访问保护(外部仍能用obj._Parent__helper访问)。 - 追问:实践中该怎么用下划线命名,什么时候用 _x、什么时候用 __x? 绝大多数情况用单下划线
_x:想表达「这是内部实现/状态/辅助方法、不是公开 API、外部请别依赖」,用_x就足够清晰(def _helper(self)、self._cache),它是最常用、最推荐的「内部」约定,无副作用;双下划线__x应该「慎用、少用」——只在「确实需要避免子类命名冲突」的特定场景才用(比如你写一个会被继承的基类、内部用了某个属性、且明确不希望任何子类无意中定义同名属性把它搞乱),而不是「想要私有」就用它(它不是私有、且名称改写会给调试、getattr/setattr、子类访问带来困惑);魔术方法__x__只实现 Python 已有的(__init__、__str__等)、别自己创造新的(那个命名空间归 Python);关键心态是接受「Python 没有真正的私有」——所有下划线都是约定或机制隔离、不是访问墙,靠团队遵守约定而非语言强制。
八、加强记忆
Python 没有真正的「私有」访问控制,用「下划线命名约定」表达访问意图,哲学是「We’re all consenting adults(我们都是成年人)」靠约定不强制。单下划线 _x(「内部/保护」约定):只是「约定俗成的提示——这是内部使用的请别在外部访问」,语言不强制(你照样能 obj._x),唯一语言效果是 from module import * 不会导入 _ 开头的名字。双下划线 __x(「名称改写 name mangling」):Python 在类内部自动把 self.__x 改写成 self._类名__x(class Base 里 __x → _Base__x,__dict__ 里真名是 _Base__x),真正目的是「避免子类意外覆盖父类的属性」——Parent 的 __x 变 _Parent__x、Child 的 __x 变 _Child__x 互不冲突,父类方法内部用 self.__x 永远拿到 _Parent__x(子类改不了),不是为了「私有保护」;关键:__x 不是私有——你仍能通过改写后的名字 obj._Base__x 访问(只是不方便)。首尾双下划线 __x__(如 __init__/__str__)是「魔术方法/特殊属性」,Python 保留、由解释器识别调用、不触发名称改写(改写规则:≥2 前导下划线 + ≤1 尾部下划线才改写)。实践:90% 场景想表达「内部使用」用单下划线 _x(够了)、只在确实需要避免子类命名冲突时才用双下划线 __x、别把 __x 当私有保护、别自造 __x__;另 x_ 尾下划线用于避免和关键字冲突(class_/type_)。一句话「_x 内部约定(提示别碰、不强制、import*不导入)、__x 触发名称改写成_类名__x(目的防子类覆盖父类属性、不是私有、仍能用_类名__x 访问)、__x__魔术方法(Python 保留不改写);Python 无真私有靠约定;内部用_x、极少防冲突用__x」。