news 2026/10/16 12:00:10

python类的私有属性和公共属性说明

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
python类的私有属性和公共属性说明

前言


「Python 的私有属性」这个说法,本身就不太准确。官方教程里写得很干脆:在 Python 中,那种「除非在对象内部,否则无法访问」的私有实例变量是不存在的。


之所以大家还总把它挂在嘴边,是因为 Python 提供了两套约定:单下划线开头(_name)表示「这是内部实现细节,别依赖」;双下划线开头(__name)则会触发编译器层面的名称改写(name mangling),把名字变成_类名__name。前者是君子协定,后者是防止子类意外撞名的技术手段——两者都不是访问控制。


本文只讲可见性约定这一面:Python 为什么没有真正的私有、_x/__x/__x__这几类名字各自的含义、名称改写到底改成了什么、以及它在实战中会怎么坑人。代码按 Python 3.8+ 给出。


一、Python 里没有真正的私有


在 C++ / Java 里,private是编译器强制执行的:从类外部访问会直接编译失败。Python 没有这种机制,原因也很直白——属性查找走的是命名空间字典,只要你知道名字,就能getattr拿到;只要你知道实例,就能vars(obj)看到它的全部属性。


Python 的设计哲学是「我们都是成年人(we are all consenting adults here)」:语言不设路障,而是靠命名约定告诉使用者「这块是内部实现」。所以你在实际项目里会看到两种现象并存:



  • 库作者用_internal命名,文档里明说「不要依赖」;

  • 使用者如果非要访问,技术上完全做得到,风险自负。


理解了这一点,后面两节的「约定」与「改写」就不会再被误解成「私有性」。


二、单下划线 _x:一种约定


官方教程:一个以单下划线开头的名字(如_spam)应当被当作非公开 API 的一部分(无论它是函数、方法还是数据成员),它属于实现细节,可能在不通知的情况下改变。


约定之外,单下划线还有一个真实的、由解释器执行的效果:在模块里,如果模块没有定义__all__,from 模块 import *只会导入不以单下划线开头的名字。也就是说,_helper这种名字不会被星号导入带出去。


# 适用于 Python 3.8+
class Cache:
def __init__(self):
self._store = {} # 约定:内部存储,外部不要直接依赖
self.hits = 0 # 公开计数

def get(self, key):
if key in self._store:
self.hits += 1
return self._store.get(key)

def put(self, key, value):
self._store[key] = value

注意_store依然可以被c._store或c.__dict__["_store"]直接读写。约定保护不了你,它只负责让人「一看就懂这是内部的东西」。


三、双下划线 __x:名称改写


以两个或更多下划线开头、且不以两个或更多下划线结尾的标识符,只要它出现在类定义内部,就会被编译器在生成代码前改写成_类名__标识符。官方规则是:类名去掉前导下划线,前面再补一个下划线,然后拼上标识符本身。


举例:在名为Wallet的类里,self.__balance会被改写成self._Wallet__balance。


# 适用于 Python 3.8+
class Wallet:
def __init__(self, balance):
self.__balance = balance # 编译期改写成 self._Wallet__balance

def deposit(self, amount):
if amount <= 0:
raise ValueError("金额必须为正")
self.__balance += amount

def show(self):
return self.__balance


w = Wallet(100)
w.deposit(50)
print(w.show()) # 推演输出:150
print(w.__dict__) # 推演输出:{'_Wallet__balance': 150}
print(w._Wallet__balance) # 推演输出:150 —— 用改写后的名字照样能访问

最后一行是重点:改写不是加密,也不是权限控制。资料里说「__x是私有属性」时,指的只是「这个属性没有出现在类的公开命名里」,而不是「拿不到」。一个vars(w)就把真名暴露了。


名称改写真正的用途是避免子类无意覆盖。因为改写的名字里带了类名,父类和子类各自的__x会落到不同的名字上:


# 适用于 Python 3.8+
class A:
def __init__(self):
self.__v = "A" # 改写为 _A__v


class B(A):
def __init__(self):
super().__init__()
self.__v = "B" # 改写为 _B__v,与 _A__v 互不干扰


b = B()
print(b._A__v, b._B__v) # 推演输出:A B
print(sorted(k for k in b.__dict__ if not k.startswith("__")))
# 推演输出:['_A__v', '_B__v']

如果要关注「类名」这一环,还得注意一个细节:改写的规则是「类名去掉前导下划线、再补一个下划线」。所以Foo、_Foo、__Foo三个类名里出现的__spam都会被改写成_Foo__spam——类名前导下划线的数量不影响结果。若类名全部由下划线组成(如_或__),则改写是恒等的,名字保持原样。


四、__x__ 与 x_ 这两类名字




名字形式官方性质说明



name公开正常 API

_name约定非公开单下划线开头:实现细节;from 模块 import *默认不带它

name_约定单下划线结尾:用来避开与关键字的冲突,如class_、import_

__name名称改写双下划线开头、不以双下划线结尾:改写成_类名__name

__name__系统保留双下划线包围:属于解释器 / 语言规范,官方建议不要自创这种名字



要澄清题面里的一个说法:单下划线开头又单下划线结尾的名字(形如_x_)并没有特殊语义,它既不会被改写,也不属于保留名。真正「保留」的是__x__这一形态——像__init__、__eq__、__slots__都是语言规范占用的名字,自己发明一个__my_thing__只会让人误以为它有特殊作用。


另外补一句版本背景:名称改写是源代码层面的文本变换,规则从 Python 2 到 Python 3 没有变化,所以在 Python 2 里旧式类和新式类的改写结果是一样的。真正随版本变化的是描述符是否生效——官方文档明确写着旧式类不支持描述符,而 Python 2 默认定义的类就是旧式类,写成class C(object):才是新式类。Python 2.7 已于2020 年 1 月 1 日停止维护(PEP 373),现在写类直接class C:即可,Python 3 里所有类都是新式类。


常见坑点


1. 以为__x是真正的私有。


❌ 认为定义self.__x之后外部绝对拿不到,于是把敏感数据(比如密钥)放心地放进去。 ✅__x只是被改写成_类名__x,vars(obj)或obj._类名__x都能读到;要保护数据得靠加密、不落库等真手段。


2. 在子类里「覆盖」父类的__x,期待多态生效。


❌ 父类self.__v、子类也写self.__v,指望子类方法能改到父类的那个值。 ✅ 两者被改写成_父类__v和_子类__v,是两个独立属性;要共享就用单下划线或约定好的公开名字。


3. 用getattr(obj, "__x")去拿改写过的属性。


❌getattr(w, "__balance")抛AttributeError,以为属性丢了。 ✅ 传改写后的名字:getattr(w, "_Wallet__balance");或者干脆查vars(w)。


4. 把单下划线当成「私有」并依赖它。


❌ 在业务代码里直接读写别人库对象的obj._store,库一升级就崩。 ✅ 把单下划线理解为「不保证稳定」的实现细节;需要长期使用的接口应当由库方提供公开方法。


5. 以为下划线开头的名字会被from 模块 import *导出。


❌ 定义了_helper()却期望星号导入能带过来。 ✅ 没有__all__时,import *只导入非下划线开头的名字;确实要导出就在__all__里显式列出。


6. 类名前导下划线带来意外撞名。


❌ 同时存在Foo与_Foo(或__Foo)两个类,各自都有__spam,结果改写名相同、互相干扰。 ✅ 记住Foo、_Foo、__Foo里的__spam都变成_Foo__spam,命名时避免这种巧合。


7. 以为改名能挡住反射和序列化。


❌ 认为__x能躲开vars()、pickle、调试器的眼睛。 ✅ 这些工具看到的都是真实键名(如_Wallet__balance),改名挡不住任何「有意的」访问。


总结




需求正确做法是否由解释器强制



标记「仅供内部使用」单下划线_name否,仅约定(import *除外)

避免子类意外撞名双下划线__name,改为_类名__name是,编译期改写

避开关键字冲突单下划线结尾name_否,仅约定

使用语言规范接口双下划线包围__name__是,语言保留

真正保护敏感数据加密 / 不落地 / 服务端校验是(靠密码学而非命名)



一句话收尾:Python 的「私有」只有名叫_name的约定和名叫__name的改名,没有访问控制;把它们当成「提示」而不是「锁」,代码设计就不会走偏。




版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 16:40:50

上下文易失?用claude-mem给命令行AI外接一块记忆硬盘

我见过不少刚接触命令行 AI 的人&#xff0c;都有同一个错觉&#xff1a;以为跟模型聊过一次之后&#xff0c;它就多少记着点你。现实是&#xff0c;只要换一个终端窗口&#xff0c;模型对你的了解约等于零。这个断层曾经让我非常沮丧&#xff0c;直到我把 claude-mem 接进日常…

作者头像 李华
网站建设 2026/10/16 0:58:29

CSS - 边框颜色:border-color 的取值与样式设置

&#x1f44b; 大家好&#xff0c;欢迎来到我的技术博客&#xff01; &#x1f4da; 在这里&#xff0c;我会分享学习笔记、实战经验与技术思考&#xff0c;力求用简单的方式讲清楚复杂的问题。 &#x1f3af; 本文将围绕CSS这个话题展开&#xff0c;希望能为你带来一些启发或实…

作者头像 李华
网站建设 2026/10/15 14:07:21

肺部CT多病种三维诊断实战:从DICOM处理到临床部署

简介&#xff1a;本资源为2019年阿里巴巴天池“数字人体”赛道——肺部CT多病种智能诊断比赛的参赛代码实现包&#xff0c;面向AI医疗方向的算法工程师、医学影像研究者及深度学习进阶学习者&#xff0c;聚焦CT图像中肺炎、结核、肺癌等多类肺部疾病的自动识别与辅助诊断任务。…

作者头像 李华
网站建设 2026/10/15 8:16:12

Python+OpenCV实时人眼识别与眨眼检测:基于EAR的疲劳监测源码实战

简介&#xff1a;这份资源面向计算机视觉初学者与需要落地人脸眼部状态检测的开发者&#xff0c;提供基于Python与OpenCV的实时人眼识别、眨眼检测和闭眼检测完整实现。代码在Ubuntu环境下开发运行&#xff0c;借助Haar级联分类器定位眼睛区域&#xff0c;并通过连续帧差异分析…

作者头像 李华
网站建设 2026/10/12 16:57:30

亚马逊广告接入 ChatGPT 后,卖家该如何布局 AI 电商营销?

随着生成式 AI 不断进入电商消费场景&#xff0c;广告流量的获取方式也在发生变化。2026 年 9 月 10 日&#xff0c;亚马逊广告宣布与 OpenAI 建立合作&#xff0c;支持广告主将亚马逊广告活动拓展至 ChatGPT。目前&#xff0c;该合作仍处于美国部分广告主参与的试点阶段。与此…

作者头像 李华