【AI大模型应用开发工程师特训笔记】第04讲(第8章):面向对象编程
目录
8.1 从“函数”到“对象”的思维升级
8.1.1 为什么需要对象?
8.1.2 类与对象:蓝图与实例
8.2 定义第一个 AI 相关的类
8.2.1 最简单的类结构
8.2.2 添加属性:记录模型的特征
8.2.3 添加方法:让对象能“做事”
8.3 构造函数 __init__ 与属性详解
8.3.1 带默认值的属性
8.3.2 类属性 vs 实例属性
8.4 封装:把内部细节藏起来
8.5 继承:基于通用模型创建专用模型
8.5.1 基本继承
8.5.2 重写方法:改变父类的行为
8.5.3 多继承(了解即可)
8.6 多态:不同的对象,相同的方法名
8.7 特殊方法(魔术方法):让对象更像“原生”数据
8.7.1 __str__ 和 __repr__:定义对象的字符串表示
8.7.2 __len__:让 len() 可用
8.7.3 __call__:让对象像函数一样被调用
8.8 综合实战:构建一个可扩展的 LLM 客户端系统
8.9 本章小结
之前我们学习了函数和模块,可以把一段代码封装成可复用的“工具”。但在更复杂的 AI 应用中(比如一个完整的对话机器人、多模型调度器、工具调用链),我们需要更高级的组织方式:把数据和操作数据的方法打包在一起,这就是面向对象编程(OOP)。本章会带你用 AI 大模型相关的例子,轻松理解类、对象、继承等概念。
8.1 从“函数”到“对象”的思维升级
8.1.1 为什么需要对象?
假设你要写一个处理 OpenAI API 调用的程序。用函数式写法,你可能会这样:
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 最简单的类结构
class LLM: """大语言模型类(最简单的版本)""" pass # 创建对象 my_model = LLM() print(type(my_model)) # <class '__main__.LLM'>8.2.2 添加属性:记录模型的特征
属性就是属于某个对象的数据。
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。
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 带默认值的属性
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。类属性:属于类本身,所有对象共享的。
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) # 28.4 封装:把内部细节藏起来
封装是指把对象的内部状态(属性)和实现细节隐藏起来,只通过公开的方法与外界交互。Python通过命名约定来实现“私有”:
单下划线
_name:表示“受保护的”,外部不应直接访问,但依然可以。双下划线
__name:名称修饰,外部不能直接访问。
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 基本继承
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 重写方法:改变父类的行为
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 框架中较少见。
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接口,然后不同模型类各自实现它。
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__:定义对象的字符串表示
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()可用
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)) # 28.7.3__call__:让对象像函数一样被调用
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 客户端系统
我们将用面向对象的方法,设计一个支持多厂商、带缓存、带计费的模型客户端。
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}")运行上述代码,输出如下内容:
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@DESKT8.9 本章小结
概念 | 含义 | AI 示例 |
|---|---|---|
类 class | 设计蓝图 |
|
对象 instance | 根据类创建的具体实例 |
|
属性 attribute | 对象的数据 |
|
方法 method | 对象的行为 |
|
| 构造函数,初始化对象 | 设置 API 密钥、默认温度 |
封装 | 隐藏内部细节 | 私有属性 |
继承 | 子类复用父类代码 |
|
多态 | 不同对象相同方法名不同行为 |
|
魔术方法 | 让对象支持 |
|
