一、核心差异总结

一句话概括:C++ 拥有编译期强制访问控制,Python 只有运行时命名约定。

C++ 的 public/protected/private 是编译器强制执行的——违规代码根本无法编译。Python 的 _/__ 前缀只是"君子协定"——技术上你总能绕过去,Python 相信"我们都是成年人"。

二、Python 的三种"伪权限"

2.1 Public(var):普通继承行为

没有任何前缀的属性就是公开的,子类和外部都可以自由访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal:
def __init__(self, name):
self.name = name # 公开属性

def speak(self): # 公开方法
return f"{self.name} makes a sound"

class Dog(Animal):
def greet(self):
return f"I'm {self.name}" # 子类直接访问,毫无障碍

dog = Dog("Buddy")
print(dog.name) # Buddy
print(dog.speak()) # Buddy makes a sound

2.2 Protected(_var):"君子协定"

单下划线前缀是一个约定,告诉其他开发者"这是内部实现,请勿直接访问"。但 Python 不会阻止你:

1
2
3
4
5
6
7
8
9
10
class Animal:
def __init__(self, name):
self._internal_id = id(name) # 约定:内部使用

class Dog(Animal):
def show_id(self):
return self._internal_id # 子类可以访问,但"不应该"

dog = Dog("Buddy")
print(dog._internal_id) # 外部也能访问,但"不应该"

C++ 中 protected 成员子类可访问、外部不可访问。Python 的 _var 没有任何强制力——它只是一条注释,告诉你"这是内部细节,别碰"。

2.3 Private(__var):名称改写机制

双下划线前缀会触发 Python 的**名称改写(Name Mangling)**机制。Python 在后台偷偷把 __var 改名为 _ClassName__var

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal:
def __init__(self):
self.__secret = "hidden" # 会被改写为 _Animal__secret

def get_secret(self):
return self.__secret # 类内部正常使用

class Dog(Animal):
def try_access(self):
# return self.__secret # AttributeError!
return self._Animal__secret # 改名后可以强行访问

dog = Dog()
# print(dog.__secret) # AttributeError!
print(dog._Animal__secret) # hidden —— 可以强行访问
print(dog.get_secret()) # hidden —— 通过公开方法正常访问

名称改写的目的不是防止外部访问,而是防止子类意外覆盖父类的属性:

1
2
3
4
5
6
7
8
9
10
11
12
class Animal:
def __init__(self):
self.__secret = "animal secret" # _Animal__secret

class Dog(Animal):
def __init__(self):
super().__init__()
self.__secret = "dog secret" # _Dog__secret,不会覆盖父类的

dog = Dog()
print(dog._Animal__secret) # animal secret —— 父类的还在
print(dog._Dog__secret) # dog secret —— 子类有自己的

三、代码对比演示

3.1 C++:编译器强制禁止访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Animal {
private:
std::string secret = "hidden";
public:
std::string get_secret() { return secret; }
};

class Dog : public Animal {
public:
void try_access() {
// std::cout << secret; // 编译错误!'secret' is private
std::cout << get_secret(); // 只能通过公开方法
}
};

int main() {
Dog dog;
// dog.secret; // 编译错误!
dog.get_secret(); // 只能这样
}

C++ 的 private 是一堵墙——编译器直接拒绝,你没有任何办法绕过。

3.2 Python:名称改写后依然可以强行访问

1
2
3
4
5
6
7
8
9
10
11
12
class Animal:
def __init__(self):
self.__secret = "hidden"

class Dog(Animal):
def try_access(self):
# return self.__secret # AttributeError
return self._Animal__secret # 可以!只是名字变了

dog = Dog()
# print(dog.__secret) # AttributeError
print(dog._Animal__secret) # hidden —— 打破封装

Python 的"私有"只是一层薄纱——改名后你依然能触及。这就是"我们都是成年人"的哲学:如果你执意要访问内部实现,那是你的选择,后果自负。

四、设计哲学

4.1 C++:防御式设计

C++ 假设开发者可能会犯错,所以用编译器来强制执行访问规则。好处是:

  • 接口契约被严格执行
  • 子类不可能意外破坏父类的内部状态
  • 重构时编译器帮你检查所有违规

代价是:

  • 灵活性差,有时为了测试需要用 friend#define private public 等 hack
  • 继承体系僵化,private 成员完全不可扩展

4.2 Python:信任式设计

Python 假设开发者知道自己在做什么,所以用命名约定来"建议"而非"强制"。好处是:

  • 极大的灵活性——需要时可以访问任何内部
  • 测试友好——无需 friend 声明就能测试私有方法
  • 动态语言的天然优势——运行时可以修改一切

代价是:

  • 没有编译期保护,错误只能在运行时发现
  • 子类可能意外覆盖父类属性(__ 前缀部分缓解了这个问题)
  • 代码维护依赖团队自律

4.3 最佳实践

场景 C++ 做法 Python 做法
公开接口 public: 无前缀
内部实现 protected: _ 前缀
防止子类覆盖 private: __ 前缀
需要访问私有成员 friend / 修改访问级别 直接用 _ClassName__var
测试私有方法 friend / 测试类 直接调用,无需特殊处理

五、总结

Python 没有真正的"私有",只有"建议不要碰"(_)和"改了名让你不太好碰"(__)。这不是设计缺陷,而是刻意的选择——在动态语言中,运行时的灵活性比编译期的严格性更有价值。

从 C++ 转 Python 时,请放下"编译器会保护我"的依赖,转而建立"命名约定即文档"的意识。好的 Python 代码靠自律而非强制来维护封装性——这也是"Pythonic"的一部分。