飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

私有变量

Python 没有 private、protected 等访问控制关键字,也不存在从对象外部绝对无法访问的"私有"实例变量。然而,通过命名约定和名称改写(name mangling)机制,Python 提供了两种层次的封装提示。

单下划线约定:内部使用

以单下划线开头的名称(如 _spam)被约定为非公有(non-public)部分。这适用于方法、函数和数据成员,表示它们是实现细节,可能在不通知的情况下改变。外部代码不应直接依赖它们。

class Buffer:
    def __init__(self, size):
        self._size = size        # 内部状态,不建议外部直接修改
        self._data = bytearray(size)
    
    def _resize(self, new_size): # 内部辅助方法
        self._data = bytearray(new_size)
        self._size = new_size
    
    def write(self, chunk):
        if len(chunk) > self._size:
            self._resize(len(chunk))
        self._data[:len(chunk)] = chunk

buf = Buffer(16)
buf.write(b'hello')
# buf._resize(8)   # 语法上允许,但违背约定

单下划线前缀纯粹是约定,解释器不会施加任何限制。from module import * 默认不会导入以单下划线开头的名称,但显式导入(如 from module import _spam)仍然可以访问。

双下划线与名称改写

以双下划线开头、最多一个下划线结尾的标识符(如 __spam)会触发名称改写(name mangling)。解释器在类定义内部将这类标识符替换为 _ClassName__spam 的形式,其中 ClassName 是去除前缀下划线后的当前类名。

class Mapping:
    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   # 原始 update 的私有副本

class MappingSubclass(Mapping):
    def update(self, keys, values):
        # 提供了新的签名,但不会破坏 __init__
        for item in zip(keys, values):
            self.items_list.append(item)

m = MappingSubclass([1, 2, 3])
print(m.items_list)   # [1, 2, 3]

在这个例子中,Mapping.__init__ 中的 self.__update 被改写为 self._Mapping__update。即使 MappingSubclass 也定义了 __update,它会被改写为 _MappingSubclass__update,两者不会冲突。因此 Mapping.__init__ 调用的始终是 Mapping 版本的 update,不受子类重写的影响。

名称改写在类定义内部无条件发生,与标识符的句法位置无关:

class Demo:
    __x = 10              # 改写为 _Demo__x
    
    def method(self):
        print(self.__x)   # 改写为 self._Demo__x
    
    def __private(self):  # 改写为 _Demo__private
        return 'hidden'

d = Demo()
d.method()              # 10
# d.__x                   # AttributeError
# d.__private()           # AttributeError

名称改写的规则细节

改写的具体规则是:对于形式为 __identifier 的名称,替换为 _ClassName__identifier。ClassName 是发生改写的那个类的名称,去除前缀下划线。

class A:
    def __init__(self):
        self.__value = 1

class B(A):
    def __init__(self):
        super().__init__()
        self.__value = 2   # 改写为 _B__value,与 A 的 _A__value 不冲突

b = B()
print(b._A__value)        # 1
print(b._B__value)        # 2

注意:名称改写不考虑继承层次。在 B 的方法中写 self.__value,总是改写为 _B__value,即使 A 中也有 __value。这是有意设计的,目的是防止子类意外覆盖父类的内部实现。

绕过名称改写

名称改写的设计目标是避免意外冲突,而非防止恶意访问。通过改写后的名称,仍然可以从外部访问:

class Secret:
    def __init__(self):
        self.__password = '123456'

s = Secret()
# print(s.__password)       # AttributeError
print(s._Secret__password)  # 123456 —— 可以访问,但不应这样做

这种能力在调试器和单元测试中偶尔有用,但生产代码中直接访问改写后的名称是糟糕的做法。

特殊边界情况

名称改写仅发生在经过字节码编译的类定义内部。传递给 exec() 或 eval() 的字符串代码不会将调用处的类名视为当前类:

class C:
    __x = 1

code = "print(self.__x)"
# exec(code, {'self': C()})   # AttributeError: 不会触发名称改写

同样,getattr()、setattr() 和 delattr() 以及直接操作 __dict__ 时,也不会自动进行名称改写:

class D:
    def __init__(self):
        self.__y = 2

d = D()
# getattr(d, '__y')           # AttributeError
print(getattr(d, '_D__y'))  # 2 —— 必须提供改写后的名称

属性访问控制的其他手段

除了命名约定和名称改写,还可以通过 property 装饰器(见属性装饰器章节)或自定义 __getattr__、__getattribute__、__setattr__ 等魔术方法来控制属性访问。但这些属于更高级的元编程技术,日常开发中应优先使用简单的命名约定。

class Controlled:
    def __init__(self):
        self._internal = 0
    
    def __setattr__(self, name, value):
        if name == 'internal':
            raise AttributeError("use _internal instead")
        super().__setattr__(name, value)

c = Controlled()
# c.internal = 1   # AttributeError: use _internal instead

需要强调的是,Python 的哲学是"我们都是负责任的成年人"(we're all consenting adults)。语言层面不强制封装,而是通过约定和文档来引导正确的使用方式。

上一页
实例变量与类变量
下一页
继承