Python 面向对象入门:类、对象与封装

面向对象系列开篇:从 面向过程 vs 面向对象 讲起,到 类与对象、self、属性、魔术方法、封装 与两个综合小案例。示例围绕文档入库、模型调用、会话管理等场景展开。


写在前面:
最近大模型实在太火了:ChatGPT、Claude、各种 Agent、RAG……朋友圈一半在晒 prompt,另一半在问「零基础能不能转 AI」。我刷到这类问题多了,也意识到一件事——热度归热度,真正想入门的人,缺的不是又一篇「震惊!AI 取代程序员」,而是一份能照着学、能写进项目里的 基础知识。

我自己也是在这条路上一点点啃过来的:Python 要补、面向对象要懂、后面还有 RAG、Agent、部署……坑不少,但啃通了确实有用。所以决定开这个专栏,把 面向对象、进阶语法、以及和大模型应用相关的工程能力 按块写清楚。不追求标题党,也不假装什么「三天精通 AGI」——能帮你少踩一个坑、少搜一次「self 到底是什么」,就算没白写——毕竟模型再聪明,也替不了你本地把代码跑通。

如果你也是 想入门 AI 大模型应用开发 的 Python 开发者,或者已经会一点语法、但一看 class LlmClient 就心里发虚——这篇从 类、对象、封装 开始,正好。系列后面还会讲继承、多态那些「读框架源码时绕不开」的东西。

火的是大模型,能把它用起来的是基本功。一起把底子打稳,再谈 RAG 和 Agent,会踏实很多。

前言

写 Python 做 RAG、Agent、模型 API 封装 时,迟早会碰到这类结构:DocumentServiceChatSessionLlmClient……它们都是 。如果只会「定义函数 + 传参」,模块一多就容易出现:

  • 同一份配置(模型名、chunk 大小)在多个函数之间 来回传
  • 改一个字段名,全局搜索替换, 漏改一处就报错
  • 日志里 print(对象) 只能看到内存地址, 排查困难

要解决这些问题,需要先把 类、对象、属性、__init__、封装 理顺。这篇是面向对象系列的第一篇;继承、多态、类方法 在下一篇单独写。


一、什么是面向对象

1 什么是面向过程

面向过程的核心是 自顶向下、逐步细化:把要做的事拆成一串 步骤,每步对应函数,按顺序调用直到出结果。

例如「用户提问 → 检索文档 → 拼 Prompt → 调模型 → 返回答案」,过程式写法可能是:

def retrieve(query: str) -> list[str]:
    return ["chunk_a", "chunk_b"]

def build_prompt(query: str, chunks: list[str]) -> str:
    ctx = "\n".join(chunks)
    return f"上下文:{ctx}\n问题:{query}"

def call_llm(prompt: str) -> str:
    return f"答:{prompt[:30]}..."

def answer_question(query: str) -> str:
    # 主流程:按步骤依次调用
    chunks = retrieve(query)
    prompt = build_prompt(query, chunks)
    return call_llm(prompt)

步骤清晰, 小脚本很合适;步骤和全局数据一多,维护和扩展成本会上升。

2 什么是面向对象

面向对象先问 「谁来做」:检索器、Prompt 构建器、模型客户端各自是 实体(对象),它们自带 数据(属性)行为(方法)

过程里的动词(检索、拼接、调用)对应到 某个对象的方法;数据不再散落全局,而挂在对象上。

3 用面向对象梳理一条 RAG 问答链路

3.1 第一步:哪些动作由哪些实体完成
动作 实体
接收用户问题 会话 ChatSession
检索相关片段 检索器 Retriever
组装 Prompt 模板器 PromptBuilder
调用大模型 客户端 LlmClient

四个实体,对应四个类;现实世界里的「一类事物」映射到代码里的 ,具体一次请求里的实例就是 对象

3.2 第二步:为实体定义属性和方法
  • 属性:描述状态,如 session_idmodel_nametop_k
  • 方法:描述行为,如 append_message()search()chat()
3.3 第三步:让对象协作完成流程
class Retriever:
    def __init__(self, top_k: int = 3):
        self.top_k = top_k

    def search(self, query: str) -> list[str]:
        return [f"chunk_{i}" for i in range(self.top_k)]


class LlmClient:
    def __init__(self, model: str):
        self.model = model

    def chat(self, prompt: str) -> str:
        return f"[{self.model}] {prompt[:40]}..."


class RagOrchestrator:
    """编排对象:持有检索器与客户端"""

    def __init__(self, retriever: Retriever, client: LlmClient):
        self.retriever = retriever
        self.client = client

    def answer(self, query: str) -> str:
        chunks = self.retriever.search(query)
        prompt = "上下文:" + "\n".join(chunks) + f"\n问题:{query}"
        return self.client.chat(prompt)


bot = RagOrchestrator(Retriever(top_k=2), LlmClient("qwen2.5"))
print(bot.answer("年假怎么算"))
3.4 面向对象编程思想迁移
过程式习惯 对象式习惯
先想「要实现什么函数」 先想「有哪些主体、各管什么」
函数 + 全局/参数传数据 属性进类,方法操作 self
功能封装成函数 主体 + 功能 一起封装成类

面向对象是一种 思维方式:用对象模拟业务里的角色,而不只是一种语法。

3.5 面向对象一定比面向过程好吗?

不绝对。对比如下:

维度 面向过程 面向对象
模块化 函数级 类级,数据与方法绑定更紧
贴近业务 线性步骤直观 实体划分贴近现实世界
前期成本 上手快 类设计需要时间
后期维护 规模大时易乱 扩展、替换实现往往更方便
执行效率 通常略直接 多一层抽象,一般可忽略

小工具、一次性脚本用过程式没问题; 长期维护的后台、Agent、RAG 服务 更常用类来划边界。

3.6 小结
  • 面向对象:用对象模拟现实,把属性与方法收到类里。
  • 与面向过程:过程式强调步骤;对象式强调 谁拥有数据、谁负责行为。对象式可看作在过程式之上的 进一步组织方式

二、面向对象的基础概念

1 面向对象中两个重要概念

1.1 对象

对象(object) 是类的一次 具体化:一次检索请求、一个聊天会话、一个文档块记录,都可以是对象。

  • 属性 存数据
  • 方法 操作数据
1.2 类

类(class) 是制造对象的 模板,规定这类对象 应该有哪些属性和方法

现实里「人类」是类别,「张三、李四」是具体的人;代码里 ChatSession 是类,sess_asess_b 是实例。
同类对象 结构相同属性值可以不同

2 类的定义

Python 3 推荐写法(写不写 (object) 等价):

class ChunkRecord:
    """文档块记录:类体里定义方法"""

    def preview(self) -> str:
        # 实例方法:第一个参数必须是 self
        return self.text[:50]

    def __init__(self, doc_id: int, text: str):
        # 构造方法:实例化时自动执行
        self.doc_id = doc_id
        self.text = text

命名习惯:

  • 类名:大驼峰,如 ChunkRecordLlmGateway
  • 方法/函数:小写+下划线,如 split_text
  • 变量:小写+下划线或团队约定的小驼峰

Python 2 曾区分经典类/新式类; Python 3 默认新式类,了解即可。

3 类的实例化(创建对象)

实例化 = 通过类得到对象。Python 不需要 new类名() 即可。

class ModelEndpoint:
    def describe(self) -> None:
        print(f"model={self.name}, url={self.base_url}")

    def __init__(self, name: str, base_url: str):
        self.name = name
        self.base_url = base_url


ep1 = ModelEndpoint("qwen-plus", "https://api.example.com/v1")
ep2 = ModelEndpoint("gpt-4o-mini", "https://api.openai.com/v1")

ep1.describe()
ep2.describe()
  • 是抽象模板,定义阶段 不占实例数据的内存
  • 对象 是具体实例,各自有一份属性空间

4 类中的 self 关键字

self 指向 当前实例。调用 obj.method() 时,Python 把 obj 自动传给 self

class ChatSession:
    def show_id(self) -> None:
        # self 即调用 show_id 的那个实例
        print(f"session={self.session_id}")

    def __init__(self, session_id: str):
        self.session_id = session_id


s1 = ChatSession("s-001")
s2 = ChatSession("s-002")
s1.show_id()  # session=s-001
s2.show_id()  # session=s-002

谁实例化,self 就指向谁。 self 不是关键字,但不要改名。


三、对象的属性添加与获取

1 什么是属性

对象 = 属性 + 方法。属性描述特征:会话 ID、模型名、温度、消息列表等。

属性可以在 类外动态添加,也可以在 __init__ 里统一初始化(推荐后者)。

2 在类的外面添加属性和获取属性

2.1 设置

语法:对象名.属性 = 值

class SessionMeta:
    pass


meta = SessionMeta()
# 实例化后再挂属性(灵活但不规范,易漏字段)
meta.user_id = "u-1001"
meta.channel = "web"
meta.lang = "zh"
2.2 获取

语法:对象名.属性

print(meta.user_id)
print(meta.channel)

3 在类的内部获取外部定义的属性

方法里通过 self.属性 访问:

class SessionMeta:
    def summary(self) -> None:
        # 假定实例已有 user_id、channel(类外或 __init__ 赋值)
        print(f"用户={self.user_id}, 渠道={self.channel}")


meta = SessionMeta()
meta.user_id = "u-1001"
meta.channel = "web"
meta.summary()

问题: 类外逐个赋值容易 拼错字段名、漏赋值
做法:__init__ 在实例化时一次性传入(见下一章)。


四、魔术方法(魔法方法)

1 什么是魔术方法

名称为 __xxx__ 的方法,在特定时机由 Python 自动调用,一般不需要手动调用。

常见魔术变量:__name____file__ 等。

2 __init__() 方法(初始化 / 构造)

触发时机: 类(...) 实例化时自动执行。
作用: 给实例赋初始属性。
参数: 第一个参数固定为 self,由解释器传入,调用时 不用写

class ChatSession:
    def __init__(self, session_id: str, user_id: str):
        # self.xxx = 参数:把构造参数绑定到实例
        self.session_id = session_id
        self.user_id = user_id
        self.messages = []


sess = ChatSession("s-8f3a", "u-2002")
print(sess.session_id, sess.user_id)

注意拼写是 __init__,不是 __int__

3 __str__() 方法

触发时机: print(对象)str(对象)
要求: 必须 return 字符串

class IndexJob:
    def __init__(self, doc_id: int, status: str):
        self.doc_id = doc_id
        self.status = status

    def __str__(self) -> str:
        return f"IndexJob(doc={self.doc_id}, status={self.status})"


job = IndexJob(101, "embedding")
print(job)  # 可读字符串,而非 0x 地址

4 __del__() 方法(析构)

触发时机: del obj 或对象被垃圾回收时 可能 调用。
常见用途: 关闭文件、断开连接( 生产环境更推荐显式 close())。

class TempFileHolder:
    def __init__(self, path: str):
        self.path = path

    def __del__(self):
        print(f"清理临时资源:{self.path}")


holder = TempFileHolder("/tmp/upload.pdf")
del holder

5 总结对比

方法 何时触发 作用
__init__ 实例化 初始化属性
__str__ print(obj) 返回字符串,便于日志/调试
__del__ 对象销毁时 释放资源(勿完全依赖)

6 综合案例:文档索引任务状态机

__init____str__ 和方法模拟「索引任务从排队到完成」:

class IndexTask:
    def __init__(self, doc_name: str):
        self.doc_name = doc_name
        self.elapsed_sec = 0       # 已处理耗时
        self.stage = "queued"      # queued / parsing / embedding / done / failed
        self.tags = []             # 附加标签,如 tenant、语言

    def tick(self, seconds: int) -> None:
        """模拟推进时间,更新阶段"""
        self.elapsed_sec += seconds
        if self.elapsed_sec < 2:
            self.stage = "queued"
        elif self.elapsed_sec < 5:
            self.stage = "parsing"
        elif self.elapsed_sec < 10:
            self.stage = "embedding"
        else:
            self.stage = "done"

    def add_tag(self, tag: str) -> None:
        self.tags.append(tag)

    def __str__(self) -> str:
        return (
            f"IndexTask(file={self.doc_name}, stage={self.stage}, "
            f"elapsed={self.elapsed_sec}s, tags={self.tags})"
        )


task = IndexTask("policy_2024.pdf")
print(task)
task.tick(6)
task.add_tag("tenant_a")
print(task)
task.tick(6)
print(task)

五、面向对象的三大特性

1 面向对象有哪些特性

常见说法:封装、继承、多态
本篇讲 封装;继承与多态在下一篇。

2 Python 中的封装

两层含义:

  1. 把属性、方法 写进类(相对过程式的「裸数据+裸函数」)
  2. 公有 / 私有 控制外部能否直接访问

3 封装中的私有属性和私有方法

类型 类内 类外直接访问
公有 可以 可以
私有(双下划线) 可以 不可以(名称改写)

Python 没有 Java 式的 private 关键字,用 __前缀 做名称改写。

4 私有属性的访问限制

class ApiCredential:
    def __init__(self, provider: str, api_key: str):
        self.provider = provider      # 公有
        self.__api_key = api_key      # 私有:外部不应直接读

    def masked_key(self) -> str:
        # 类内仍可访问 __api_key
        return self.__api_key[:4] + "****"


cred = ApiCredential("openai", "sk-abcdef123456")
print(cred.provider)
print(cred.masked_key())
# print(cred.__api_key)  # AttributeError

私有成员 默认不被子类当作同名继承字段直接使用(名称改写机制导致)。

5 私有属性设置与访问接口

对外提供 get_xxx / set_xxx 或属性方法,在接口里做校验:

class GenerationParams:
    def __init__(self, temperature: float):
        self.__temperature = 0.7
        self.set_temperature(temperature)

    def get_temperature(self) -> float:
        return self.__temperature

    def set_temperature(self, value: float) -> None:
        if not isinstance(value, (int, float)):
            raise TypeError("temperature 必须是数字")
        if not 0 <= value <= 2:
            raise ValueError("temperature 应在 [0, 2]")
        self.__temperature = float(value)


params = GenerationParams(0.8)
params.set_temperature(1.0)
print(params.get_temperature())

6 私有方法

定义方式相同:方法名 __method_name

class WithdrawFlow:
    def __init__(self, balance: float):
        self.__balance = balance

    def __verify_pin(self) -> bool:
        return True

    def __deduct(self, amount: float) -> None:
        self.__balance -= amount

    def withdraw(self, amount: float) -> None:
        # 对外一个入口,内部步骤私有
        if not self.__verify_pin():
            raise PermissionError("验证失败")
        self.__deduct(amount)


flow = WithdrawFlow(1000)
flow.withdraw(200)

7 封装性有何意义

  1. 按模块划分职责:RAG 里检索、生成、会话各成一个类。
  2. 过滤非法数据:在 set_temperature 里校验,而不是谁都能改字段。
  3. 降低使用复杂度:调用方只调 withdraw(),不必知道内部几步验证。
class UserProfile:
    def __init__(self, name: str, age: int):
        self.__name = name
        self.__age = age

    def set_info(self, name: str, age: int) -> None:
        if not isinstance(name, str):
            raise TypeError("name 必须是字符串")
        if not isinstance(age, int):
            raise TypeError("age 必须是整数")
        self.__name = name
        self.__age = age

    def brief(self) -> str:
        return f"{self.__name}, {self.__age}"


p = UserProfile("alice", 25)
p.set_info("bob", 30)
print(p.brief())

六、面向对象的实战案例

1 模型评测记录案例

需求:记录 模型名、得分,并按分数档打印评价(90+ 优秀,80+ 良好,依此类推)。

class ModelEvalRecord:
    def __init__(self, model_name: str, score: float):
        self.model_name = model_name
        self.score = score

    def print_grade(self) -> None:
        s = self.score
        if s >= 90:
            level = "优秀"
        elif s >= 80:
            level = "良好"
        elif s >= 70:
            level = "中等"
        elif s >= 60:
            level = "及格"
        else:
            level = "不及格"
        print(f"模型={self.model_name}, 得分={s}, 评价={level}")


ModelEvalRecord("qwen2.5-7b", 86).print_grade()
ModelEvalRecord("local-llm", 58).print_grade()

2 Token 预算追踪案例

需求:某会话有 Token 预算;每次 chat 消耗 token,summarize 也会消耗;用 __str__ 查看当前状态。

class TokenBudget:
    def __init__(self, name: str, budget: int):
        self.name = name
        self.budget = budget
        self.used = 0

    def chat(self, tokens: int) -> None:
        self.used += tokens

    def summarize(self, tokens: int) -> None:
        self.used += tokens

    def remaining(self) -> int:
        return self.budget - self.used

    def __str__(self) -> str:
        return (
            f"TokenBudget(name={self.name}, used={self.used}, "
            f"remaining={self.remaining()})"
        )


budget = TokenBudget("session-01", budget=8000)
budget.chat(1200)
budget.summarize(800)
print(budget)
budget.chat(5000)
print(budget)

常见问题

Q:__init__ 是创建对象吗?
严格说创建由 __new__ 完成(很少重写);__init__ 只负责 初始化属性

Q:类必须写 (object) 吗?
Python 3 不必,默认继承 object

Q:双下划线是真·加密吗?
不是,只是名称改写;API Key 等敏感信息靠 环境变量与权限,不靠 __

Q:三大特性这篇讲到哪?
本篇 封装;继承、多态见下一篇。


和 AI 大模型开发的关系

文档入库流水线

class DocumentIndexer:
    def __init__(self, tenant_id: str, chunk_size: int, embed_model: str):
        self.tenant_id = tenant_id
        self.chunk_size = chunk_size
        self.embed_model = embed_model

    def run(self, file_path: str) -> str:
        doc_id = self.__register_meta(file_path)
        chunks = self.__split(file_path)
        self.__embed_and_store(doc_id, chunks)
        return doc_id

    def __register_meta(self, file_path: str) -> str:
        return "doc_xxx"

    def __split(self, file_path: str) -> list:
        return []

    def __embed_and_store(self, doc_id: str, chunks: list) -> None:
        pass

多租户会话与日志

class ChatSession:
    def __init__(self, session_id: str, user_id: str, system_prompt: str):
        self.session_id = session_id
        self.user_id = user_id
        self.system_prompt = system_prompt
        self.messages = []

    def append(self, role: str, content: str) -> None:
        self.messages.append({"role": role, "content": content})

    def __str__(self) -> str:
        return f"ChatSession(id={self.session_id}, turns={len(self.messages)})"

模型网关与 Key 封装

class LlmGateway:
    def __init__(self, model: str, api_key: str, timeout: int = 120):
        self.model = model
        self.__api_key = api_key
        self.timeout = timeout

    def get_headers(self) -> dict:
        return {"Authorization": f"Bearer {self.__api_key}"}

    def __str__(self) -> str:
        return f"LlmGateway(model={self.model!r}, timeout={self.timeout})"

Agent 工具入口(过程式 vs 对象式)

class BaseTool:
    name = "base"

    def run(self, **kwargs) -> str:
        return ""


class KnowledgeSearchTool(BaseTool):
    name = "kb_search"

    def run(self, **kwargs) -> str:
        q = kwargs.get("query", "")
        return f"[kb] results for {q}"


def dispatch(tool: BaseTool, **kwargs) -> str:
    return tool.run(**kwargs)


print(dispatch(KnowledgeSearchTool(), query="年假政策"))

写在最后
如果把 AI 大模型应用开发 比作打 Boss:
你现在刚学完这篇 类、对象、封装,大概相当于 出了新手村、装备从木棍换成铁剑——还远没到挑战最终 Boss 的时候,但至少不会在村口被史莱姆(self 报错、AttributeError)秒杀了。

后面还有 继承、多态、装饰器、网络、数据库 这些小 Boss 和中 Boss 排着队。每啃完一块,读 LangChain、写 RAG、封装模型 API 时就不会全程「这行代码我看不懂但不敢问」。

不用一次全会,今天能把类写对、对象能实例化、知道封装是干嘛的,就已经比只会 print(“hello”) 离实战近了一大步。
下一篇继续点技能树——继承和多态 已经在副本门口等你了。加油,冒险者。

小结

  1. 过程式 按步骤拆函数; 对象式 按实体划分类,属性与方法进类。
  2. 是模板, 类() 得对象; self 代表当前实例。
  3. 属性推荐在 __init__ 里初始化; __str__ 改善日志输出。
  4. 封装 收拢数据与流程, __ 配合 get/set 做访问控制。
  5. 下一篇写 继承、重写、super、MRO、多态 以及 类属性、类方法、静态方法

#Python #面向对象 #封装 #大模型应用开发 #RAG #Agent

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐