大模型再火,也得先过 class 这一关
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 封装 时,迟早会碰到这类结构:DocumentService、ChatSession、LlmClient……它们都是 类。如果只会「定义函数 + 传参」,模块一多就容易出现:
- 同一份配置(模型名、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_id、model_name、top_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_a、sess_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
命名习惯:
- 类名:大驼峰,如
ChunkRecord、LlmGateway - 方法/函数:小写+下划线,如
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 中的封装
两层含义:
- 把属性、方法 写进类(相对过程式的「裸数据+裸函数」)
- 用 公有 / 私有 控制外部能否直接访问
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 封装性有何意义
- 按模块划分职责:RAG 里检索、生成、会话各成一个类。
- 过滤非法数据:在
set_temperature里校验,而不是谁都能改字段。 - 降低使用复杂度:调用方只调
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”) 离实战近了一大步。
下一篇继续点技能树——继承和多态 已经在副本门口等你了。加油,冒险者。
小结
- 过程式 按步骤拆函数; 对象式 按实体划分类,属性与方法进类。
- 类 是模板,
类()得对象;self代表当前实例。 - 属性推荐在
__init__里初始化;__str__改善日志输出。 - 封装 收拢数据与流程,
__配合 get/set 做访问控制。 - 下一篇写 继承、重写、super、MRO、多态 以及 类属性、类方法、静态方法。
#Python #面向对象 #封装 #大模型应用开发 #RAG #Agent
更多推荐




所有评论(0)