私有变量与名称改写
Python 没有真正的"私有"成员(像 C++ 或 Java 的 private)。所有属性都是公开的,但社区约定和语言机制提供了两种层次的"隐私"保护:单下划线前缀(约定)和双下划线前缀(名称改写)。
单下划线前缀:_name
class BankAccount(object):
def __init__(self):
self._balance = 0 # "内部使用,不要直接访问"
def deposit(self, amount):
self._balance += amount
def get_balance(self):
return self._balance
_balance 前面的单下划线是一种温和提示:它告诉其他程序员"这个属性是内部实现细节,外部不应直接访问"。但技术上仍然可以访问:
account = BankAccount()
account.deposit(100)
print account._balance # 100,可以访问,但不推荐
单下划线前缀还会影响 from module import * 的行为:以 _ 开头的名称不会被导入(除非在 __all__ 中显式列出)。
双下划线前缀:__name
class Mapping(object):
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # 私有副本
class MappingSubclass(Mapping):
def update(self, keys, values):
for item in zip(keys, values):
self.items_list.append(item)
m = MappingSubclass([1, 2, 3])
print m.items_list # [1, 2, 3]
__update 是 Mapping 类的私有方法。当 MappingSubclass 覆盖 update 时,Mapping.__init__ 中调用的 self.__update 仍然指向 Mapping.update,不会被 MappingSubclass.update 覆盖。
名称改写机制
双下划线前缀会触发名称改写(name mangling):Python 把 __name 改写为 _ClassName__name:
class MyClass(object):
def __init__(self):
self.__secret = 42
obj = MyClass()
print obj.__secret # AttributeError: 'MyClass' object has no attribute '__secret'
print obj._MyClass__secret # 42!名称被改写了
__secret 在类内部被改写为 _MyClass__secret。这种改写发生在编译时,不考虑标识符的句法位置——只要在类定义内部出现,就会被改写。
改写的目的
名称改写不是为了安全(仍然可以通过 _ClassName__name 访问),而是为了避免子类意外覆盖:
class Base(object):
def __init__(self):
self.__value = 10
def get_value(self):
return self.__value
class Derived(Base):
def __init__(self):
Base.__init__(self)
self.__value = 20 # 不会覆盖 Base 的 __value!
def get_derived_value(self):
return self.__value
d = Derived()
print d.get_value() # 10,Base 的 __value
print d.get_derived_value() # 20,Derived 的 __value
print d._Base__value # 10
print d._Derived__value # 20
Base.__value 被改写为 _Base__value,Derived.__value 被改写为 _Derived__value,两者互不干扰。
使用建议
| 前缀 | 含义 | 访问方式 |
|---|---|---|
| 无 | 公开 API | obj.name |
_ | 内部实现,可以访问但不推荐 | obj._name |
__ | 私有实现,防止子类覆盖 | obj._ClassName__name |
__xxx__ | 魔术方法,Python 特殊用途 | obj.__init__() |
原则:
- 公开 API 不要加下划线
- 内部实现用单下划线
_ - 需要防止子类覆盖时用双下划线
__ - 不要滥用双下划线——它让调试和测试更困难
- 绝对不要用
__来"保护"数据,Python 没有真正的私有
与 C++/Java 的对比
| 语言 | 私有机制 | 实际效果 |
|---|---|---|
| C++ | private | 编译期检查,无法访问 |
| Java | private | 编译期检查,反射可绕过 |
| Python | __name | 名称改写,约定保护 |
Python 的设计哲学是"我们都是成年人"(We're all consenting adults)——相信程序员会遵守约定,而不是用语言机制强制限制。这种设计让元编程、调试、测试更灵活,但也要求程序员有更高的自律。