2025-10-07 Python不基础 19——私有对象

文章目录

  • [1. 私有访问限制](#1. 私有访问限制)
  • [2. 私有的意义](#2. 私有的意义)
  • [3. 实现原理:Name Mangling](#3. 实现原理:Name Mangling)
  • [4. 注意事项](#4. 注意事项)

本文参考视频链接:

Python 中没有像 Java、C++ 那样通过 private 关键字声明私有变量的语法,而是通过变量/方法的命名规则实现私有特性,核心规则如下:

将变量/方法的名称前添加「至少两个下划线」,且名称后至多添加一个下划线,即可将其定义为私有成员。

最常用的形式是「前加两个下划线」:

python 复制代码
class A:
    def __init__(self):
        # 定义私有变量 __v(前加两个下划线)
        self.__v = 0  # 私有变量
        self.public_v = 1  # 普通公有变量

    # 定义私有方法 __print_v(前加两个下划线)
    def __print_v(self):
        print(self.__v)

    # 公有方法:内部访问私有变量和私有方法
    def access_private(self):
        self.__v += 1  # 类内可修改私有变量
        self.__print_v()  # 类内可调用私有方法

1. 私有访问限制

私有变量/方法的核心作用是限制外部直接访问,仅允许在类的内部(即类的成员函数中)使用,具体表现为:

① 类外直接访问私有成员会报错

若在类的外部尝试直接读取或修改私有变量/调用私有方法,Python 会抛出 AttributeError,提示"对象没有该属性":

python 复制代码
obj = A()

# 1. 访问普通公有变量:正常
print(obj.public_v)  # 输出:1

# 2. 直接访问私有变量:报错
print(obj.__v)  # 报错:AttributeError: 'A' object has no attribute '__v'

# 3. 直接调用私有方法:报错
obj.__print_v()  # 报错:AttributeError: 'A' object has no attribute '__print_v'

② 类内可自由访问私有成员

通过类的公有方法,可以间接访问或修改私有成员(符合"封装"思想,控制访问入口):

python 复制代码
obj = A()
obj.access_private()  # 输出:1(类内成功修改并打印私有变量 __v)

2. 私有的意义

私有变量的设计并非"为了隐藏而隐藏",而是为了解决实际开发中的问题,主要有两大价值:

① 强化封装性,防止误用

  • 通过命名明确告知代码使用者:"该成员仅为类内部逻辑服务,外部不应直接操作",避免因外部随意修改导致类的内部逻辑混乱。
  • 例如:一个"用户类"中,密码 __password 设为私有,仅通过 set_password()(校验密码复杂度)和 get_password()(加密返回)两个公有方法访问,确保密码的安全性。

② 避免继承中的成员覆盖问题

当子类与父类存在同名成员时,子类会覆盖父类的成员,可能导致父类的逻辑失效。私有成员不会被继承覆盖,可解决此问题。

python 复制代码
class A:
    # 父类的公有类属性:存储合法关键字
    valid_keywords = ["A"]

    def __init__(self, **kwargs):
        # 仅处理 valid_keywords 中的关键字
        for k, v in kwargs.items():
            if k in self.valid_keywords:
                print(f"A: {k}={v}")

class B(A):
    # 子类覆盖父类的 valid_keywords
    valid_keywords = ["B"]

    def __init__(self, **kwargs):
        # 子类处理自己的关键字
        for k, v in kwargs.items():
            if k in self.valid_keywords:
                print(f"B: {k}={v}")
        # 调用父类初始化
        super().__init__(** kwargs)

# 实例化 B,传入 A=2、B=3
obj = B(A=2, B=3)  
# 输出:B: B=3(父类 A 的 __init__ 中,self.valid_keywords 被子类覆盖为 ["B"],故 A=2 未被处理)

将父类和子类的 valid_keywords 改为私有变量 __valid_keywords,私有成员不会被覆盖,父类逻辑正常:

python 复制代码
class A:
    # 父类的私有类属性(前加两个下划线)
    __valid_keywords = ["A"]

    def __init__(self, **kwargs):
        # 访问父类私有属性 __valid_keywords(不会被子类覆盖)
        for k, v in kwargs.items():
            if k in self.__valid_keywords:
                print(f"A: {k}={v}")

class B(A):
    # 子类的私有类属性(与父类私有属性名称相同,但不会覆盖)
    __valid_keywords = ["B"]

    def __init__(self, **kwargs):
        # 访问子类私有属性 __valid_keywords
        for k, v in kwargs.items():
            if k in self.__valid_keywords:
                print(f"B: {k}={v}")
        super().__init__(** kwargs)

# 实例化 B,传入 A=2、B=3
obj = B(A=2, B=3)  
# 输出:B: B=3 → A: A=2(父类私有属性未被覆盖,正常处理 A=2)

③ 防止类内方法被意外重载

若将类内核心方法设为私有(如 __core_logic()),子类无法重载该方法,可确保类内部调用时始终执行自身定义的逻辑,避免子类重载导致的逻辑混乱。

3. 实现原理:Name Mangling

Python 的私有机制并非"强制禁止访问",而是通过编译期的名字改编(Name Mangling)实现,本质是"给私有成员改了个名字",具体规则如下:

当 Python 编译代码时,会将类中定义的私有成员 __name(前两个下划线)自动改名为:
_类名__name(格式:下划线 + 类名 + 原私有成员名)

例如:

  • A 中的私有变量 __v,会被改编为 _A__v
  • B 中的私有变量 __valid_keywords,会被改编为 _B__valid_keywords

通过访问改编后的名称,可在类外"绕过"私有限制(不推荐,违背封装设计):

python 复制代码
obj = A()

# 直接访问改编后的私有变量(类外)
print(obj._A__v)  # 输出:0(未报错,成功读取私有变量 __v)

# 直接修改改编后的私有变量(类外)
obj._A__v = 5
obj.access_private()  # 输出:6(私有变量被修改)
  • Python 的私有机制是「语法层面的命名隐藏」,而非「底层强制限制」,无法完全阻止外部访问(与 Java/C++ 的 private 不同);
  • 名字改编发生在编译期 (将源代码转为字节码时),仅对类定义内部的私有成员生效,动态操作(如 setattr、直接修改 __dict__)不会触发改编。

4. 注意事项

由于私有机制依赖编译期的名字改编,以下场景中私有特性会失效,需特别注意:

① 动态赋值私有成员不触发名字改编

通过 setattr 函数动态给对象添加"私有命名"的属性,不会触发名字改编,本质是普通公有属性:

python 复制代码
obj = A()
# 动态添加属性 __v1(前两个下划线)
setattr(obj, "__v1", 10)

# 直接访问 __v1:正常(未被改编)
print(obj.__v1)  # 输出:10(无报错,说明是公有属性)

② 直接操作 __dict__ 不触发名字改编

对象的 __dict__ 属性存储了其所有实例属性,直接修改 __dict__ 添加"私有命名"的键,不会触发名字改编:

python 复制代码
obj = A()
# 直接给 __dict__ 添加键 __v2
obj.__dict__["__v2"] = 20

# 直接访问 __v2:正常
print(obj.__v2)  # 输出:20(无报错)

③ 外部函数赋值给类方法不触发名字改编

若将类外定义的函数赋值给类的"私有方法名",该方法不会被改编,本质是公有方法:

python 复制代码
# 类外定义函数
def external_func(self):
    print("外部函数")

class A:
    # 将外部函数赋值给"私有方法名" __ext_func
    __ext_func = external_func

obj = A()
# 直接调用 __ext_func:正常(未被改编)
obj.__ext_func()  # 输出:外部函数(无报错)

Python 社区的设计哲学是"信任开发者",私有机制更多是"提醒"而非"强制"。实际开发中,若仅需"告知外部不应访问",也可使用「单下划线命名」(如 _v)------虽无访问限制,但约定俗成表示"内部成员,外部慎用"。

相关推荐
程序员爱钓鱼几秒前
Python编程实战 - 函数与模块化编程 - 匿名函数(lambda)
后端·python·ipython
朝新_几秒前
【SpringMVC】SpringMVC 请求与响应全解析:从 Cookie/Session 到状态码、Header 配置
java·开发语言·笔记·springmvc·javaee
2501_938782092 分钟前
从实例到单例:Objective-C 单例类的线程安全实现方案
开发语言·macos·objective-c
浪裡遊3 分钟前
css面试题1
开发语言·前端·javascript·css·vue.js·node.js
喜欢吃燃面9 分钟前
C++:红黑树
开发语言·c++·学习
佳哥的技术分享13 分钟前
kotlin基于MVVM架构构建项目
android·开发语言·kotlin
清空mega17 分钟前
Flask入门学习指南
后端·python·flask
zero13_小葵司17 分钟前
JavaScript 性能优化系列(六)接口调用优化 - 6.4 错误重试策略:智能重试机制,提高请求成功率
开发语言·javascript·ecmascript
无敌最俊朗@18 分钟前
SQLite 约束:INTEGER PRIMARY KEY 与 ROWID
java·开发语言
DARLING Zero two♡21 分钟前
Profile-Guided Optimization(PGO):Rust 性能优化的终极武器
开发语言·性能优化·rust