目录
[8.1 从"函数"到"对象"的思维升级](#8.1 从“函数”到“对象”的思维升级)
[8.1.1 为什么需要对象?](#8.1.1 为什么需要对象?)
[8.1.2 类与对象:蓝图与实例](#8.1.2 类与对象:蓝图与实例)
[8.2 定义第一个 AI 相关的类](#8.2 定义第一个 AI 相关的类)
[8.2.1 最简单的类结构](#8.2.1 最简单的类结构)
[8.2.2 添加属性:记录模型的特征](#8.2.2 添加属性:记录模型的特征)
[8.2.3 添加方法:让对象能"做事"](#8.2.3 添加方法:让对象能“做事”)
[8.3 构造函数 init 与属性详解](#8.3 构造函数 init 与属性详解)
[8.3.1 带默认值的属性](#8.3.1 带默认值的属性)
[8.3.2 类属性 vs 实例属性](#8.3.2 类属性 vs 实例属性)
[8.4 封装:把内部细节藏起来](#8.4 封装:把内部细节藏起来)
[8.5 继承:基于通用模型创建专用模型](#8.5 继承:基于通用模型创建专用模型)
[8.5.1 基本继承](#8.5.1 基本继承)
[8.5.2 重写方法:改变父类的行为](#8.5.2 重写方法:改变父类的行为)
[8.5.3 多继承(了解即可)](#8.5.3 多继承(了解即可))
[8.6 多态:不同的对象,相同的方法名](#8.6 多态:不同的对象,相同的方法名)
[8.7 特殊方法(魔术方法):让对象更像"原生"数据](#8.7 特殊方法(魔术方法):让对象更像“原生”数据)
[8.7.1 str 和 repr:定义对象的字符串表示](#8.7.1 str 和 repr:定义对象的字符串表示)
[8.7.2 len:让 len() 可用](#8.7.2 len:让 len() 可用)
[8.7.3 call:让对象像函数一样被调用](#8.7.3 call:让对象像函数一样被调用)
[8.8 综合实战:构建一个可扩展的 LLM 客户端系统](#8.8 综合实战:构建一个可扩展的 LLM 客户端系统)
[8.9 本章小结](#8.9 本章小结)
之前我们学习了函数和模块,可以把一段代码封装成可复用的"工具"。但在更复杂的 AI 应用中(比如一个完整的对话机器人、多模型调度器、工具调用链),我们需要更高级的组织方式:把数据和操作数据的方法打包在一起,这就是面向对象编程(OOP)。本章会带你用 AI 大模型相关的例子,轻松理解类、对象、继承等概念。
8.1 从"函数"到"对象"的思维升级
8.1.1 为什么需要对象?
假设你要写一个处理 OpenAI API 调用的程序。用函数式写法,你可能会这样:
python
def call_llm(prompt, model, temperature):
# 调用 API 的逻辑
return response
def count_tokens(text):
return len(text.split())
def estimate_cost(model, tokens):
# 根据模型计算费用
return cost
但缺点很明显:model、temperature 这些参数要反复传递,而且不同用户的不同模型配置难以管理。如果能把"一个模型实例"的所有属性 (模型名、温度、最大 token)和它的行为(调用、计费、token 统计)打包在一起,代码会清晰很多。
8.1.2 类与对象:蓝图与实例
-
类:一个蓝图或模板,定义了一类事物应该有什么属性(数据)和方法(能做什么)。
-
对象:根据蓝图创建出来的具体实例,每个对象可以有不同的属性值。
类比:类就像是汽车的设计图纸,对象就是根据图纸造出来的一辆辆真车。每辆车有自己的颜色、车牌号,但它们都遵循同一个设计(能加速、刹车)。
8.2 定义第一个 AI 相关的类
8.2.1 最简单的类结构
python
class LLM:
"""大语言模型类(最简单的版本)"""
pass
# 创建对象
my_model = LLM()
print(type(my_model)) # <class '__main__.LLM'>
8.2.2 添加属性:记录模型的特征
属性就是属于某个对象的数据。
python
class LLM:
def __init__(self, name, context_length):
"""构造函数:在创建对象时自动调用,用于初始化属性"""
self.name = name # 实例属性
self.context_length = context_length
# 创建两个不同的模型对象
gpt4 = LLM("gpt-4", 8192)
claude = LLM("claude-3", 200000)
print(gpt4.name) # gpt-4
print(claude.context_length) # 200000
-
__init__是 Python 中的构造函数,第一个参数必须是self,代表对象本身。 -
self.name = name表示给这个对象添加一个叫name的属性,并把它赋值为传入的参数。
8.2.3 添加方法:让对象能"做事"
方法就是定义在类内部的函数,第一个参数也是 self。
python
class LLM:
def __init__(self, name, context_length, price_per_1k):
self.name = name
self.context_length = context_length
self.price_per_1k = price_per_1k
def estimate_cost(self, input_tokens, output_tokens):
"""估算本次调用的费用(美元)"""
total_tokens = input_tokens + output_tokens
return total_tokens / 1000 * self.price_per_1k
def can_handle(self, prompt_length):
"""检查提示词长度是否在上下文限制内"""
return prompt_length <= self.context_length
# 使用
gpt4 = LLM("gpt-4", 8192, 0.03)
cost = gpt4.estimate_cost(1500, 300)
print(f"费用: ${cost}") # 费用: $0.054
print(gpt4.can_handle(10000)) # False
注意:调用方法时,
self不需要手动传递,Python 会自动把gpt4作为self传进去。
8.3 构造函数 __init__ 与属性详解
8.3.1 带默认值的属性
python
class Conversation:
def __init__(self, system_prompt="你是AI助手", model="gpt-3.5"):
self.system_prompt = system_prompt
self.model = model
self.messages = [] # 存储对话历史
def add_user_message(self, content):
self.messages.append({"role": "user", "content": content})
def add_assistant_message(self, content):
self.messages.append({"role": "assistant", "content": content})
def show_history(self):
for msg in self.messages:
print(f"{msg['role']}: {msg['content']}")
# 创建对话
chat = Conversation("你是一位Python专家")
chat.add_user_message("如何定义类?")
chat.add_assistant_message("用class关键字...")
chat.show_history()
8.3.2 类属性 vs 实例属性
在Python中,类属性 是直接定义在类体中的变量,被该类的所有实例共享 ,通过类名.属性或实例.属性访问(但实例修改时通常只创建实例属性,不会影响类属性);而实例属性 是在实例方法内通过self.属性定义的,每个实例独立拥有自己的副本,修改实例属性不会影响其他实例或类属性。简单区分:类属性属于"类蓝图",实例属性属于"每个具体对象"。
-
实例属性 :每个对象独有的,如上例中的
self.name。 -
类属性:属于类本身,所有对象共享的。
python
class LLM:
# 类属性:记录所有已知模型的计数
total_models_created = 0
def __init__(self, name):
self.name = name
LLM.total_models_created += 1 # 每次创建对象加 1
print(LLM.total_models_created) # 0
gpt = LLM("gpt-4")
claude = LLM("claude-3")
print(LLM.total_models_created) # 2
8.4 封装:把内部细节藏起来
封装是指把对象的内部状态(属性)和实现细节隐藏起来 ,只通过公开的方法与外界交互。Python 通过命名约定来实现"私有":
-
单下划线
_name:表示"受保护的",外部不应直接访问,但依然可以。 -
双下划线
__name:名称修饰,外部不能直接访问。
python
class APIClient:
def __init__(self, api_key):
self.__api_key = api_key # 私有属性
self._base_url = "https://api.openai.com" # 受保护
def call(self, prompt):
# 内部使用 __api_key 发起请求
return f"使用密钥 {self.__api_key[:5]}... 调用成功,回复:{prompt}"
client = APIClient("sk-123456")
print(client.call("Hello"))
# print(client.__api_key) # AttributeError
print(client._base_url) # 可以访问,但约定不要这样做
封装的好处:你可以随时改变内部实现(如更换 API 提供商),只要公开方法签名不变,调用方代码完全不需要修改。
8.5 继承:基于通用模型创建专用模型
继承允许你定义一个子类 ,它自动拥有父类的所有属性和方法,并可以增加或重写(覆盖)一些功能。
8.5.1 基本继承
python
class BaseLLM:
def __init__(self, name, context_length):
self.name = name
self.context_length = context_length
def generate(self, prompt):
return f"{self.name} 生成: {prompt}"
# 子类:支持函数调用的模型
class FunctionCallingLLM(BaseLLM):
def call_function(self, func_name, args):
return f"{self.name} 正在调用函数 {func_name},参数 {args}"
gpt4 = FunctionCallingLLM("gpt-4", 8192)
print(gpt4.generate("Hello")) # 继承的方法
print(gpt4.call_function("get_weather", {"city": "Beijing"})) # 子类新方法
8.5.2 重写方法:改变父类的行为
python
class GPT4(BaseLLM):
def generate(self, prompt):
# 调用父类方法并补充内容
base_response = super().generate(prompt)
return base_response + " [额外:使用高精度计算]"
model = GPT4("gpt-4", 8192)
print(model.generate("讲个笑话"))
# 输出:gpt-4 生成: 讲个笑话 [额外:使用高精度计算]
8.5.3 多继承(了解即可)
Python 支持一个子类继承多个父类,但容易复杂。AI 框架中较少见。
python
class Streamable:
def stream(self):
return "流式输出"
class Cacheable:
def cache(self):
return "缓存命中"
class AdvancedLLM(BaseLLM, Streamable, Cacheable):
pass
adv = AdvancedLLM("claude", 100000)
print(adv.stream())
8.6 多态:不同的对象,相同的方法名
多态允许不同类的对象对同一个方法名做出不同的响应 。在 AI 中,你可以定义一个统一的 generate 接口,然后不同模型类各自实现它。
python
class OpenAIModel:
def generate(self, prompt):
return "OpenAI 回复: " + prompt
class AnthropicModel:
def generate(self, prompt):
return "Anthropic 回复: " + prompt
def chat(model, prompt):
print(model.generate(prompt))
gpt = OpenAIModel()
claude = AnthropicModel()
chat(gpt, "你好") # OpenAI 回复: 你好
chat(claude, "你好") # Anthropic 回复: 你好
8.7 特殊方法(魔术方法):让对象更像"原生"数据
在 Python 中,有一类以双下划线开头和结尾的方法(比如 init、str、len),它们被称为"魔术方法"。魔术方法不是让你直接调用的,而是 Python 在特定场景下自动调用的。通过实现这些方法,你可以让自己的类实例表现得像 Python 内置的列表、字符串、数字一样,支持 len()、str()、+ 等操作。
零基础理解:可以把魔术方法理解为"潜规则"或"约定"。比如,当 Python 看到 len(x) 时,它会自动去调用 x 的 len 方法。你只要在类里写好了 len 方法,Python 就知道你的对象也能问"有多长"。
常用魔术方法举例
-
init:初始化方法。当你创建对象时(比如p = Person()),Python 自动调用它,用来设置对象的初始属性。 -
str:字符串表示。当你使用print(对象)或str(对象)时,Python 自动调用它,返回一个"给人看"的友好字符串。 -
repr:开发时用的字符串表示。在交互环境中直接输入对象名时显示,通常返回一个可以用来重建对象的表达式。 -
len:长度。当你调用len(对象)时,Python自动调用它,返回一个整数。 -
add:加法。当你使用对象1 + 对象2时,Python自动调用左边对象的add方法,把右边的对象传进去。
8.7.1 __str__ 和 __repr__:定义对象的字符串表示
python
class LLM:
def __init__(self, name, context):
self.name = name
self.context = context
def __str__(self):
return f"LLM({self.name}, ctx={self.context})"
model = LLM("gpt-4", 8192)
print(model) # LLM(gpt-4, ctx=8192)
8.7.2 __len__:让 len() 可用
python
class ConversationHistory:
def __init__(self):
self.messages = []
def add(self, msg):
self.messages.append(msg)
def __len__(self):
return len(self.messages)
hist = ConversationHistory()
hist.add("hello")
hist.add("world")
print(len(hist)) # 2
8.7.3 __call__:让对象像函数一样被调用
python
class TemperatureScaler:
def __init__(self, factor=1.2):
self.factor = factor
def __call__(self, temperature):
return temperature * self.factor
scale = TemperatureScaler(1.5)
print(scale(0.7)) # 1.05(对象名后面加括号,就像函数调用)
这在 AI 框架中常用于配置某些可调用的参数变换。
8.8 综合实战:构建一个可扩展的 LLM 客户端系统
我们将用面向对象的方法,设计一个支持多厂商、带缓存、带计费的模型客户端。
python
import hashlib
import time
# ---------- 1. 基础模型接口(抽象基类,但不严格要求)----------
class BaseLLM:
def __init__(self, name, price_per_1k):
self.name = name
self.price_per_1k = price_per_1k
self.total_cost = 0.0
def generate(self, prompt, **kwargs):
"""子类必须实现具体调用逻辑"""
raise NotImplementedError
def _record_cost(self, input_tokens, output_tokens):
cost = (input_tokens + output_tokens) / 1000 * self.price_per_1k
self.total_cost += cost
return cost
# ---------- 2. 具体模型实现 ----------
class OpenAIClient(BaseLLM):
def __init__(self, api_key, model_name="gpt-3.5-turbo"):
super().__init__(model_name, price_per_1k=0.002)
self.api_key = api_key
def generate(self, prompt, temperature=0.7):
# 模拟 API 调用(实际会用 requests 库)
print(f"[OpenAI] 调用 {self.name},temperature={temperature}")
# 模拟 token 计数
input_tokens = len(prompt) // 4
output_text = f"OpenAI 回复:{prompt}"
output_tokens = len(output_text) // 4
cost = self._record_cost(input_tokens, output_tokens)
return {
"text": output_text,
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"cost": cost
}
class AnthropicClient(BaseLLM):
def __init__(self, api_key, model_name="claude-3"):
super().__init__(model_name, price_per_1k=0.025)
self.api_key = api_key
def generate(self, prompt, temperature=0.7):
print(f"[Anthropic] 调用 {self.name},temperature={temperature}")
input_tokens = len(prompt) // 4
output_text = f"Claude 回复:{prompt}"
output_tokens = len(output_text) // 4
cost = self._record_cost(input_tokens, output_tokens)
return {
"text": output_text,
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"cost": cost
}
# ---------- 3. 带缓存的装饰器模式(简化) ----------
class CachedLLM:
def __init__(self, llm_client):
self.llm = llm_client
self.cache = {}
def generate(self, prompt, **kwargs):
key = hashlib.md5(f"{prompt}_{kwargs}".encode()).hexdigest()
if key in self.cache:
print("缓存命中!")
return self.cache[key]
else:
result = self.llm.generate(prompt, **kwargs)
self.cache[key] = result
return result
# ---------- 4. 使用 ----------
if __name__ == "__main__":
# 创建原始客户端
openai = OpenAIClient(api_key="sk-xxx", model_name="gpt-4")
# 增加缓存功能
cached_openai = CachedLLM(openai)
# 第一次调用
res1 = cached_openai.generate("讲个笑话", temperature=0.8)
print(res1["text"])
print(f"花费: ${res1['cost']:.4f}")
# 第二次调用相同 prompt(会命中缓存)
res2 = cached_openai.generate("讲个笑话", temperature=0.8)
print(res2["text"])
print(f"总成本: {openai.total_cost}")
运行上述代码,输出如下内容:
python
tianpeng@DESKTOP-4L1UF5S:~/my-ai-service$ poetry run python src/my_ai_service/loop.py
[OpenAI] 调用 gpt-4,temperature=0.8
OpenAI 回复:讲个笑话
花费: $0.0000
缓存命中!
OpenAI 回复:讲个笑话
总成本: 8e-06
tianpeng@DESKT
8.9 本章小结
| 概念 | 含义 | AI 示例 |
|---|---|---|
| 类 class | 设计蓝图 | class LLM: |
| 对象 instance | 根据类创建的具体实例 | gpt4 = LLM("gpt-4") |
| 属性 attribute | 对象的数据 | self.name, self.context_length |
| 方法 method | 对象的行为 | def generate(self, prompt): |
__init__ |
构造函数,初始化对象 | 设置 API 密钥、默认温度 |
| 封装 | 隐藏内部细节 | 私有属性 __api_key |
| 继承 | 子类复用 父类 代码 | class FunctionCallingLLM(BaseLLM): |
| 多态 | 不同对象相同方法名不同行为 | model.generate(prompt) 对不同模型调不同的实现 |
| 魔术方法 | 让对象支持 Python 内置操作 |
__str__, __len__, __call__ |