码农高天
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)