Python借名称修饰防范继承覆盖 动态注入暴露机制硬伤

【python】加俩下划线就私有了?聊聊python的私有变量机制。为什么说它不是真的私有变量? — 码农高天

码农高天

Python私有变量仅在编译期通过静态重命名实现隔离以防子类覆盖,因缺乏强制访问控制而无法在动态属性注入时生效。

AI-assisted summary of 【python】加俩下划线就私有了?聊聊python的私有变量机制。为什么说它不是真的私有变量? — original source. Check the original for full context.

Source published:

Key ideas

  • Python 的私有变量并非严格意义上的语言级强制隔离,底层仅是通过编译期名称修饰(Name Mangling)将变量改写为 `_类名__变量名` 的形式。 (5:00)
  • 在继承关系中,私有变量能有效防止子类无意中覆盖父类的属性或方法,解决类似参数校验等场景下的覆盖 bug。 (0:00)
  • 名称修饰属于编译期静态行为,使用 `setattr()`、直接操作 `__dict__` 或外部动态挂载的方法无法自动触发修饰机制。 (5:00)

Detailed notes

## Background 该视频由“码农高天”于 2024 年 4 月 19 日发布在 YouTube 平台上。视频简介指出,本期内容主要介绍 Python 的私有变量机制,探讨该机制本身的优劣以及在实际开发中为何需要使用它。

## TL;DR 视频深入探讨了 Python 中通过双下划线命名的私有变量机制及其在继承场景下防止属性被子类覆盖的实用价值。码农高天从字节码层面揭示了其底层“名称修饰”(Name Mangling)的本质,即在编译阶段静态地将变量名改写为 `_类名__变量名`,因此它并非传统意义上的强制访问隔离。同时,视频强调该机制核心在于防范误用而非绝对安全,并指出了使用 `setattr` 或直接操作 `__dict__` 等动态赋值手段无法触发修饰的典型踩坑场景。

## Atmosphere & style 整体氛围严谨求真且直击底层,带有鲜明硬核技术博主的拆解风格。讲解节奏紧凑、直奔主题,通过对比 C++、Java 的概念与 Python 实际字节码行为,层层拨开语言设计的表象。在面对“Python 私有变量是不是摆设”的行业争议时,博主给出了客观理性且极具工程视角的反驳,让观众在清晰领悟底层逻辑的同时,感受到实用主义编程思想的魅力。

## Bulletin - [05:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=300s) Python 的私有变量并非严格意义上的语言级强制隔离,底层仅是通过编译期名称修饰(Name Mangling)将变量改写为 `_类名__变量名` 的形式。 - [00:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=0s) 在继承关系中,私有变量能有效防止子类无意中覆盖父类的属性或方法,解决类似参数校验等场景下的覆盖 bug。 - [05:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=300s) 名称修饰属于编译期静态行为,使用 `setattr()`、直接操作 `__dict__` 或外部动态挂载的方法无法自动触发修饰机制。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=0s) Python 定义私有成员(属性或方法)的规则是在名称前加至少两个下划线且末尾最多一个下划线,无需额外的 private 关键字。 - [00:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=0s) 在类外部直接访问双下划线开头的私有属性会抛出 AttributeError,但在类内部方法中可以正常读取与调用。 - [00:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=0s) 父子类若共用公有属性进行参数过滤或校验,子类的同名属性会覆盖父类属性,导致父类读取到错误的状态。 - [00:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=0s) 将父类内部的关键属性声明为私有变量,可以确保基类逻辑始终访问自身版本,不会被派生类意外覆盖。 - [00:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=0s) 私有方法的应用场景之一是防止类内部的方法实现被子类重写,保证类内调用的稳定性。 - [05:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=300s) Python 私有变量的底层机制是 Name Mangling,在字节码编译期自动将 `__v` 改写为 `_类名__v`。 - [05:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=300s) 通过显式拼接修饰后的名称(如 `o._A__v`),外部仍然能够直接访问并修改私有变量,因此它不是绝对安全的访问控制机制。 - [05:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=300s) 私有变量机制的核心价值在于明确接口边界、防止误用及避免继承冲突,不能因其可以被技术手段绕过而全盘否定其工程意义。 - [05:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=300s) 通过 `setattr(self, '__v', 0)` 动态设置属性不会触发名称修饰,生成的仍是未经改写的 `__v` 属性。 - [05:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=300s) 直接向实例的 `self.__dict__` 中插入双下划线变量同样绕过了编译期解析,无法享受到私有变量的隔离效果。 - [05:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=300s) 在类外部定义函数并动态赋值绑定为类方法,无法自动获取当前类的名称空间前缀修饰。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=0s) Python 私有变量的定义与继承中的典型应用

在 Python 类的成员函数中,通常可以直接对实例属性(member variable)赋值,比如写 `self.v = 0`,实例化对象 `o` 后也可以随意通过 `o.v = 1` 来修改该值。但如果在 Java 或 C++ 这类语言中,会有专门的“私有成员”(Private Member / Private Variable)机制。码农高天解释道:“这个私有成员是什么意思呢?就是只有在这个类自己的方法里才可以使用或者是读取这个变量,而在这个类之外这个成员你是 access 不到的。”

在 Python 中,将类变量或方法定义为私有并非通过关键字,而是通过命名规则实现的:在属性、变量或方法名字前加上至少两个下划线(`__`),且末尾最多只能有一个下划线。最常见的做法就是在名称前加双下划线(例如 `self.__v = 0`)。如果在类外部直接尝试访问 `o.__v`,Python 会抛出错误,提示对象并没有该属性;但如果类内部定义了一个方法 `print_v` 来打印 `self.__v`,外部调用 `o.print_v()` 就能正常输出。同理,这一规则对方法也生效,如果在函数名前加双下划线(如 `def __print_v(self):`),外部实例也无法直接调用。

这种设计让类的封装变得更加严格,不仅明确告知使用者哪些属性不该碰,还防范了误用。除此之外,它在继承场景下尤其实用。码农高天举了一个典型的父子类参数校验例子:基类 `A` 和派生类 `B`(`B` 继承自 `A`)都维护了一个名为 `valid_keywords` 的类成员,用来约束 `__init__` 中允许接收的关键字参数(kwargs)。在常规实现中,子类 `B` 在初始化时挑出自己需要的参数,剩下的传给基类 `A`。然而,如果直接使用常规成员 `self.valid_keywords`,当传入 `A=2, B=3` 实例化 `B` 时,会发现只有 `B=3` 被处理,本该传给基类 `A` 的 `A=2` 却被判定为非法。导致这个 bug 的根源在于:“当你去读取这个 self.valid_keywords 的时候,由于 self 是一个 B object,所以它的 valid_keywords 已经被覆盖掉了。” 基类 `A` 读取到的实际上是子类覆盖后的属性。

为了解决该覆盖问题,只需将 `valid_keywords` 改为私有变量 `__valid_keywords`。因为私有变量不会被子类继承和覆盖,基类 `A` 在执行时将始终访问到属于它自己的定义,从而使传入的参数都能被正确识别。类似地,如果类开发者不希望某个内部成员方法在未来被子类重写覆盖,也可以将其声明为私有方法,确保类内部调用时永远指向最初定义的那个版本。

```mermaid flowchart TD accTitle: Inheritance conflict and name mangling solution accDescr: Shows how child class overrides public attributes in parent class, and how private variables isolate them. subgraph Bug["公有属性存在被覆盖的风险"] ParentA["Class A: valid_keywords = {'A'}"] ChildB["Class B: valid_keywords = {'B'}"] ChildB -->|"继承并覆盖"| ParentA SelfAccess["self.valid_keywords 读取"] -->|"由于实例是 B,读取到的是子类的值"| ChildB end subgraph Fix["改用私有变量隔离"] PrivParent["Class A: __valid_keywords"] PrivChild["Class B: __valid_keywords"] MangParent["_A__valid_keywords"] MangChild["_B__valid_keywords"] PrivParent -.->|"编译期改名"| MangParent PrivChild -.->|"编译期改名"| MangChild SafeAccess["基类内 self.__valid_keywords"] -->|"始终指向基类私有版本"| MangParent end ```

[05:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=300s) Name Mangling 原理、设计争议与动态赋值的失效陷阱

揭开底层机制来看,Python 实现私有变量的方式极为质朴,这一机制被称为“名称修饰”(Name Mangling)。从编译后的字节码中可以清晰看到,在执行 `store_attr` 保存属性时,实际写入的属性名被改写成了 `_A__v`(即一个下划线 + 类名 + 变量原名)。码农高天点出其核心本质:“也就是说在 Python 里面,所谓的私有变量机制仅仅是把这个变量改了个名而已。”如果你在类外部直接调用 `o._A__v`,Python 会毫不客气地直接打印出该值。

正因如此,许多开发者会质疑这根本算不上真正的私有变量。但码农高天认为不能全盘否定该机制:编程语言支持私有属性主要有两层意义——第一层是在语言层面上强制避免外部访问,保障安全和稳定性,在这点上 Python 确实谈不上严格;但第二层意义在于防止由于代码复杂而造成的误用,以及避免内部变量被派生类意外覆盖。码农高天明确反驳了虚无主义的论调:“并不是说你可以刻意的通过某些方式绕开这个机制,这个机制就是垃圾了。”

理解了 Name Mangling 是在“编译期”(将源码解析为字节码阶段)静态完成的这一特性后,开发者就能避开很多常见的“骚操作”踩坑场景。由于名称改写不是运行时的动态行为,以下几种操作均无法触发私有变量机制: - 使用 `setattr(self, "__v", 0)` 动态设置属性,此时属性名不会被自动加上 `_ClassName` 前缀。 - 直接向实例的 `self.__dict__["__v"]` 字典中赋值。 - 在类外部定义函数,并在类内部通过简单赋值将其绑定为类方法(例如 `class_method = external_func`)。

使用者脑子里必须时刻牢记:私有变量机制完全依赖于编译器在解析类定义作用域时对属性和方法名称所做的字符串改写;一旦脱离了编译期语法块的语境,该机制便不再生效。

- 命名规范要求:必须以至少两个下划线(`__`)开头,末尾最多只能有一个下划线。 - 名称修饰规则:`__var` 在类 `A` 的定义中会被静态编译为 `_A__var`。 - 动态属性注入(如 `setattr` 或直接修改 `__dict__`)绕过了词法解析,不会发生修饰。 - 外部动态挂载方法不会自动携带类命名空间前缀,无法享受到私有变量的隐藏效果。

## Notable quotes > [05:00](https://www.youtube.com/watch?v=5a4qwP4PwDM&t=300s) 在 Python 里面,所谓的私有变量机制仅仅是把这个变量改了个名而已。 — 码农高天

## Mentioned 码农高天, Python, Java, C++

## Video description 这期视频介绍一下python的私有变量机制,讨论一下这个机制本身的优劣,以及为什么要使用它。

[Source](https://www.youtube.com/watch?v=5a4qwP4PwDM)

中文简报

Python借名称修饰防范继承覆盖 动态注入暴露机制硬伤 Python私有变量仅在编译期通过静态重命名实现隔离以防子类覆盖,因缺乏强制访问控制而无法在动态属性注入时生效。 视频深入探讨了 Python 中通过双下划线命名的私有变量机制及其在继承场景下防止属性被子类覆盖的实用价值。码农高天从字节码层面揭示了其底层“名称修饰”(Name Mangling)的本质,即在编译阶段静态地将变量名改写为 `_类名__变量名`,因此它并非传统意义上的强制访问隔离。同时,视频强调该机制核心在于防范误用而非绝对安全,并指出了使用 `setattr` 或直接操作 `__dict__` 等动态赋值手段无法触发修饰的典型踩坑场景。 Python 的私有变量并非严格意义上的语言级强制隔离,底层仅是通过编译期名称修饰(Name Mangling)将变量改写为 `_类名__变量名` 的形式。 (5:00) 在继承关系中,私有变量能有效防止子类无意中覆盖父类的属性或方法,解决类似参数校验等场景下的覆盖 bug。 (0:00) 名称修饰属于编译期静态行为,使用 `setattr()`、直接操作 `__dict__` 或外部动态挂载的方法无法自动触发修饰机制。 (5:00)

Reading summary

码农高天指出,Python 的私有变量机制并非真正的语言级强制访问隔离,而是通过在编译期改写名称的“名称修饰”(Name Mangling)实现的;尽管外部仍可刻意绕过,但它能有效防止属性误用及继承场景下的意外覆盖。 私有变量的命名规则与封装效果 (0:00) 在 Python 中,声明私有变量或私有方法不需要特殊关键字,而是依赖命名规则:名称前需带有至少两个下划线(`__`),且末尾最多只能有一个下划线(如 `self.__v = 0`)。在类外部直接访问 `o.__v` 时,Python 会抛出对象不存在该属性的错误;但若通过类内部方法调用,则可以正常读取和执行。这套规则在类外部建立了初步的封装边界,明确提示使用者哪些内部属性不应被随意触碰。 解决继承时的属性覆盖冲突 (0:00) 私有属性在继承关系中能够发挥关键的隔离作用。例如,基类 `A` 与派生类 `B` 同时定义公有成员 `valid_keywords` 来校验参数时,子类实例会直接覆盖基类的同名属性,导致基类在初始化校验时因读取到子类的属性而发生逻辑错误。若将该属性重构为私有变量 `__valid_keywords`,基类在内部访问时将始终绑定自身定义的版本,从而彻底规避派生类属性或方法对基类逻辑的非预期重写。 名称修饰原理与非真私有争议 (5:00) 从编译后的字节码观察,Python 实现私有机制的方式实质是“名称修饰”(Name Mangling)。当执行属性保存操作时,类 `A` 内的 `__v` 会被静态改写为 `_A__v`(即单下划线 + 类名 + 原变量名)。因为外部只要通过 `o._A__v` 就能直接读取和修改该值,许多开发者质疑其并非真正的私有变量。但码农高天强调,不能因此视其为无用设计;私有机制的核心价值不仅在于强制阻止访问,更在于防止代码复杂化后的误调用与意外继承覆盖。 编译期改写特性与动态赋值陷阱 (5:00) Name Mangling 发生在源码解析为字节码的编译阶段,而非运行时动态执行。因此,任何脱离了类定义词法作用域的操作都无法触发修饰机制。开发者若使用 `setattr(self, "__v", 0)`、直接写入字典 `self.__dict__["__v"]`,或将类外部定义的独立函数直接赋值给类方法,Python 都不会自动补齐类名前缀,导致私有保护机制完全失效。 Python 的私有属性本质上是依靠编译期字符串改写来实现的防呆与隔离机制,虽未提供绝对的安全防护,但开发者在理解其静态生效前提后,可有效利用它规避继承重写冲突并规范接口使用。

Source quotes

[5:00] 码农高天: 在 Python 里面,所谓的私有变量机制仅仅是把这个变量改了个名而已。

Original transcript

[0:00] 这期视频呢我们来聊一下私有对象啊private variable我们知道我们可以在Python类的成员函数里面对一个object的member variable进行复制比如像第三行这样我们可以写self.v=0那在我们建立了这个类的对象之后呢我们也可以通过O.V等于1去改变V这个值这个呢就是我们正常的object里面的member variable那你如果学过渣瓦或者C++就会知道啊在这些语言里面还有一个东西叫做私有成员Private Member with a private variable这个私有成员是什么意思呢就是只有在这个类自己的方法里才可以使用或者是读取这个变量而在这个类之外这个成员你是access不到的那在python中把一个类里面的变量定义成私有变量的方法是通过它的名字来实现的你可以把这个属性或者变量或者方法的名字前面加至少两个下方线同时后面有至多一个下滑线来把它定义成私有变量我们最常见的方式就是把这个名字前面加两个下滑线我们看在a这个class的初始化里面,我们做self.下号线下号线v等于0那当我们在这个类外面的代码里想使用这个O点下方向下方向V的时候它就会报错我们可以看到这里他就告诉你说A这个object没有下温下温键V这个属性而如果我们给A写一个方法print v,让它打印self.肖像肖像 v然后在这个类之外使用printV它就可以正常的打出这个值了当然就像我们前面说的这个规则对于方法也有效如果我们print v 之前加两个下滑线的话这个 o 就也没有办法使用这个方法了那私有变量这个概念的存在当然是有意义的一个比较明显的好处就是他让我们的封装变得更加严格了我们可以通过对变量和函数的命名告诉这段代码的使用者这个变量或者这个函数是你不应该用的同时还提供了一个机制防止这些变量和函数被误用当然除此之外它还有些其他的好处尤其是当我们涉及到继承的时候我们来看左边这个例子我们有两个class A 和 BB继承了A这两个class的共同特点呢都是有一个class member叫做valid keywords这个valid keywords里面保存了在初始化的时候这个keyword args里面有哪些keyword是可以被接受的当我们尝试初始化的时候B先从这些keyword里面挑他需要的然后挑剩下的再传给A但是在这为了简单起见我们就直接把他拿到的这个keyword跟value给打印出来了那当你一眼看上去的时候这个程序是没有什么问题的class A需要A这个keyword然后class B需要B这个keyword但是当我们建立一个对象的时候我们看二常O等于B 然后keyword给了一个A等于2 B等于3我们会发现只有B3这种QRX被使用了而本该被传到这个鸡肋A里面的keyword A并没有被认为是合法的这里面的问题就在第五行当你去读取这个 self.valid keywords的时候由于self是一个be object所以它的VALUE KEYWORD 已经被覆盖掉了第五行的这个Self.Valid Keywords跟第十四行的Valid Keywords是一样的所以在class A 的这个 init 函数里面,尽管它被调用了,但它不认为那个 A 是一个合法的 keyword argument我们可以通过把 valid keywords 变成私有变量来解决这个问题由于私有变量是不会被继承和覆盖的所以第五行的这个下关下限 valid keywords将会永远指向class a 里面定义的这个下关下限 valid keywords我们可以看到右边程序的运行结果B和A都被打印出来了那当然除了在classMember里面可以使用这个技巧之外在classMethod里面也可以使用这个技巧有时候我可能不希望我类里面的某些成员函数被重宰我就可以把它变成私有的这样我可以保证在我自己的类里面调用这个函数的时候它永远会调用的是我定义的这个函数而不是继承我的类重新定义的同名函数好那说完了应用我们来说一说原理在python中实现私有变量的机制实际上非常简单 [5:00] 它叫做Name Mangeling什么意思呢?我们看一下这段代码编译出来的字节码就知道了我们看在左边这段代码编译出来的字节码里面这个store attr也就是保存属性的这个部分它保存到的是下滑线A下滑线下滑线B换句话说python实现私有变量的方式本质上就是把你这个self.下方线下方线v等于0给在编译期变成了self.a 下方线v等于0也就是在你的这个属性前面加了一个下滑线再加上你的class名事实上如果我们在这里打印O.下方向A 下方向V的话他会直接把这个值给打印出来也就是说在python里面所谓的私有变量机制仅仅是把这个变量改了个名而已那讲到这里肯定有人会说这叫什么私有变料对吧一点也不私有确实相比于Java或者C++的私有变量机制Python的私有变量机制非常的简单也确实谈不上一个真实的私有变量因为你可以非常容易的绕过它这个机制很多人可能知道这个原理就每天跟你说啊Python没有私有变量Python的私有变量没有意义真的是这样吗当一个程序语言去支持私有变量这种机制的时候,它起码有两层的意义。第一可能是为了安全或者稳定性的原因他要在语言层面上强制避免你去使用这些私有变量从这个方向上讲Python的私有变量当然是不合格的但是私有变量还有另外一层的意义就是防止你误用这个私有变量可能你这个代码比较复杂然后你不小心误用了一个本来应该是私有的变量同时他提供了一个不让变量被自己的衍生类继承的机制那从这些角度上讲,Python的私有变量显然是有价值有意义的并不是说你可以刻意的通过某些方式绕开这个机制这个机制就是垃圾了好了在了解了这个机制的原理之后你就会意识到在使用这个机制的时候你得小心点别掉到坑里我们说过这个所谓NameMengaline的过程是在编译期实现的也就是当它把你的语言代码变为自解码的时候实现的所以他是不支持很多你其他的骚操作的比如在这里我们使用 set ATTR 函数把self. 变成0那这种使用方式显然是没有办法触发python的私有变量机制的同样的 如果我们直接对O的下方向性Dict做复值也是没有办法处罚私有变量机制的再比如如果我们把函数定义在这个class的外面然后在class里 仅仅是把外面定义的函数 复制给class method它也是没有办法触发私有变量机制的当然由于这个机制的特殊性肯定还会有很多其他的croner case会导致他没有办法正常工作所以大家在使用这个机制的时候,脑子里面一定要有一个概念这个机制是通过编译期把class的定义里面的这些method 包括 attribute包括member给改一个名来实现的那当你掌握了这个原理之后在大部分的情况下你应该就能判断这种方式到底能不能工作好了 这期视频就到这里 喜欢的话记得点下关注 别忘了意见三连哦字幕志愿者 杨茜茜