在 Python 中,protected 是一种“约定”或“惯例”,而不是一种强制性的语法规则,它告诉其他程序员:“这个成员是受保护的,请你在类的外部不要直接访问它,它只供子类和本类内部使用。”
核心概念
Python 主要通过在成员名称(变量或方法名)前加单个下划线 _ 来表示 protected 成员。
语法:
class MyClass:
def __init__(self):
self._protected_variable = "I am a protected variable"
def _protected_method(self):
print("This is a protected method")
protected 的行为和约定
-
名称修饰 (Name Mangling): 这是最关键的一点,Python 会对以单个下划线
_开头的成员进行一种特殊的“名称修饰”,当你在类的外部访问它时,Python 会在名称前加上类名和一个下划线。MyClass中的_protected_variable在外部会被视为_MyClass__protected_variable。- 目的: 这种修饰的主要目的不是“禁止”访问,而是防止“意外覆盖”,当你创建一个子类时,如果子类也定义了一个同名的
_protected成员,Python 的名称修饰机制可以确保父类和子类的成员不会互相干扰。
-
访问权限(可访问性): 从技术上讲,
protected成员仍然可以在类的外部被访问和修改,Python 不会像 Java 或 C++ 那样抛出编译时或运行时错误,它依赖于开发者的自律。
protected vs. private vs. public
为了更好地理解 protected,我们通常会将它与 private 和 public 放在一起比较。
| 访问修饰符 | 语法 | 描述 | 访问权限 |
|---|---|---|---|
| Public (公共) | member |
没有任何下划线前缀,任何地方都可以访问。 | 类内部、子类、类外部均可访问 |
| Protected (受保护) | _member |
单个下划线前缀,一种约定,表示“请勿外部访问”。 | 类内部、子类可以访问,外部技术上可访问但不建议 |
| Private (私有) | __member |
双下划线前缀,表示“严格私有,外部不应访问”。 | 仅限类内部访问,外部访问会触发名称修饰 |
代码示例对比
class MyClass:
def __init__(self):
# Public 成员
self.public_var = "I am public"
# Protected 成员
self._protected_var = "I am protected"
# Private 成员
self.__private_var = "I am private"
def public_method(self):
print("This is a public method")
print(f"Inside class, can access public: {self.public_var}")
print(f"Inside class, can access protected: {self._protected_var}")
print(f"Inside class, can access private: {self.__private_var}")
# --- 创建实例 ---
obj = MyClass()
# --- 1. 访问 Public 成员 ---
print("--- Accessing Public ---")
print(obj.public_var) # 输出: I am public
obj.public_method() # 输出所有内部访问信息
# --- 2. 访问 Protected 成员 ---
print("\n--- Accessing Protected ---")
# 技术上可以访问,但强烈不推荐
print(obj._protected_var) # 输出: I am protected
# --- 3. 访问 Private 成员 ---
print("\n--- Accessing Private ---")
# 直接访问会报错!
try:
print(obj.__private_var)
except AttributeError as e:
print(f"Error: {e}")
print("Direct access to private member is blocked.")
# --- 4. 访问修饰后的 Private 成员 ---
print("\n--- Accessing Mangled Private Member ---")
# 通过名称修饰可以“绕过”限制,但这同样不推荐!
print(obj._MyClass__private_var) # 输出: I am private
输出:
--- Accessing Public ---
I am public
Inside class, can access public: I am public
Inside class, can access protected: I am protected
Inside class, can access private: I am private
--- Accessing Protected ---
I am protected
--- Accessing Private ---
Error: 'MyClass' object has no attribute '__private_var'
Direct access to private member is blocked.
--- Accessing Mangled Private Member ---
I am private
为什么需要 protected?(使用场景)
protected 的主要作用是实现封装,这是一种面向对象编程的核心原则。
- 内部实现细节: 一个类的某些属性或方法可能是为了实现某个功能而设计的“内部工具”,它们不应该被类的使用者直接操作,因为它们可能会在未来版本中改变。
- 防止误用: 如果一个属性是受保护的,其他开发者在你的代码库中使用你的类时,会通过 IDE 或文档知道“这个变量不应该碰”,从而避免破坏对象内部状态。
- 为子类提供扩展点:
protected成员允许子类继承并修改或利用父类的内部逻辑,同时又阻止了外部的直接干预,这是protected与private的关键区别。
示例:protected 在继承中的使用
class Animal:
def __init__(self, name):
self.name = name
self._energy = 100 # 能量是内部状态,受保护
def _decrease_energy(self, amount):
"""这是一个受保护的方法,用于减少能量"""
self._energy -= amount
if self._energy < 0:
self._energy = 0
print(f"{self.name}'s energy is now {self._energy}")
def make_sound(self):
print(f"{self.name} makes a sound.")
# 内部方法调用受保护方法
self._decrease_energy(5)
class Dog(Animal):
def bark(self):
print(f"{self.name} barks loudly!")
# 子类可以访问并修改父类的受保护成员
self._decrease_energy(10) # 子类使用父类的受保护方法
# --- 使用 ---
my_dog = Dog("Buddy")
my_dog.make_sound() # Buddy makes a sound. Buddy's energy is now 95
my_dog.bark() # Buddy barks loudly! Buddy's energy is now 85
# 外部代码不应该直接操作 _energy
# my_dog._energy = 200 # 虽然技术上可以,但这破坏了封装原则,是不好的做法
总结与最佳实践
| 特性 | public |
protected |
private |
|---|---|---|---|
| 前缀 | 无 | _ |
__ |
| 含义 | 任何人都可以用 | “请勿碰,除非你是子类” | “严格私有,仅供我内部使用” |
| 外部访问 | ✅ 允许 | ⚠️ 技术允许,但强烈不推荐 | ❌ 直接访问会报错 |
| 主要用途 | 类的公共 API | 供子类扩展的内部实现细节 | 完全不希望被外部或子类访问的内部实现 |
Python 之禅(The Zen of Python)中的一句话很好地总结了这一点:
"We are all consenting adults here." (我们都是这里的成年人。)
这意味着 Python 相信开发者会遵守社区的约定,而不是用严格的语法来限制他们。
最佳实践建议:
- 默认使用
public: 如果一个成员是你类公共 API 的一部分,就让它保持public(无下划线)。 - 谨慎使用
protected: 当一个成员只供本类和其子类使用,并且你不希望它被外部代码“意外”使用时,使用_前缀,这是最常见的“内部”成员标记。 - 仅在必要时使用
private: 当你有一个成员,你确定绝对不希望子类访问或修改它时,才使用__前缀,这通常用于那些非常底层的、与具体实现紧密相关的工具变量或方法,防止子类无意中破坏它们。
遵循这些约定,你的代码将更具可读性、可维护性,也更符合 Python 的设计哲学。
