为什么需要接口 链接到标题
随着项目规模不断的增加,越来越多的第三方包,库需要我们去适配,同时需要去考虑可扩展性,可替换性,可替换尤为重要因为这样才能在不同的提供商方便切换,比较典型的例子有:
- 计算机硬件驱动,同样的功能比如网络,声音,我们可以用不同的网卡,麦克风和音响
- 大模型提供商,可以在同一服务使用不同的大模型,比如
Openclaw可以切换不同的模型 - 数据库选型,都是增删改查,但是我们可以使用不同的数据库去切换
但是,不同的网卡,音响或麦克风他们的厂家都不一致,他们的内部运作方式肯定也不一样,各个大模型(Qwen, Chatgpt, Claude)他们的模型的输入输出的格式都有出入,我们该如何去兼容所有厂商呢?
答案是: 给所有外部提供商提供统一的接口,不用担心外部具体的实现,只关心出入的标准
对于大模型,程序内部我们只需要一个可以返回内容的函数
def chat(user_input: str) -> reply: str:
...
return reply
不同的大模型提供商,我们可以为他们写一个同样的 chat 函数,并且函数参数和返回值都高度保持一致(这有点像函数的重载),这样我们的业务层就只需要调用 chat 函数,不需要关注内部实现,然后在函数内部就去适配,去转换输入输出类型
# 将自定义的 LLMTool 转换成 OpenAi 可接受的格式
def _to_openai_tools(
self, tools: list[LLMTool]
) -> list[ChatCompletionToolUnionParam]:
openai_tools: list[ChatCompletionToolUnionParam] = []
for tool in tools:
function_def: FunctionDefinition = {
"name": tool.name,
"description": tool.description,
"parameters": tool.input_schema,
}
openai_tool: ChatCompletionFunctionToolParam = {
"type": "function",
"function": function_def,
}
openai_tools.append(openai_tool)
return openai_tools
接口如何玩 链接到标题
Python 处理接口的方式经历了一个非常有趣的演变:从纯粹的动态“鸭子类型”,进化到了结合静态类型检查的 Protocol(结构化子类型)。
这不仅保留了 Python 的灵活性,还解决了大型项目中代码难以维护和缺乏 IDE 提示的痛点。我们来一步步拆解。
- 鸭子类型 (Duck Typing):灵活但不可控 鸭子类型是 Python 最核心的动态特性之一。它的哲学是:“如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子。”
在代码中,这意味着我们不关心对象的真实类型,只关心它是否实现了特定的方法。
class Duck:
def quack(self):
print("嘎嘎嘎!")
class Dog:
def quack(self):
print("汪?但我也会嘎嘎叫!")
# 只要传入的对象有 quack() 方法,这段代码就能跑通
def make_sound(animal):
animal.quack()
make_sound(Duck()) # 正常
make_sound(Dog()) # 正常,因为 Dog 恰好也有 quack 方法
痛点: 当项目变大时,make_sound(animal) 里的 animal 到底需要什么样的方法?IDE 无法为你自动补全,你只能去翻源码。如果传入了一个没有 quack 方法的对象,只有在运行到那一行时才会报错抛出 AttributeError。
- 引入 Protocol:静态鸭子类型 为了解决上述痛点,Python 3.8 在 typing 模块中引入了 Protocol(基于 PEP 544)。它被称为结构化子类型 (Structural Subtyping),也就是“带静态类型检查的鸭子类型”。
它的核心逻辑是:你定义一个“协议”,规定类必须具备哪些方法或属性。只要一个类实现了这些方法,类型检查器(如 mypy)就认为它是这个协议的子类,而完全不需要它去显式继承这个协议。
怎么写 Protocol? 定义 Protocol 时,我们通常只写方法签名,函数体用 … (Ellipsis) 占位。
from typing import Protocol
# 1. 定义一个协议(接口)
class Quacker(Protocol):
def quack(self) -> None:
...
# 2. 具体的类不需要继承 Quacker!
class RealDuck:
def quack(self) -> None:
print("嘎嘎嘎!")
class RubberDuck:
def quack(self) -> None:
print("吱吱吱!")
class Cat:
def meow(self) -> None:
print("喵喵喵!")
# 3. 在函数参数中标注协议类型
def trigger_sound(animal: Quacker) -> None:
animal.quack()
trigger_sound(RealDuck()) # ✅ mypy 类型检查通过
trigger_sound(RubberDuck()) # ✅ mypy 类型检查通过
# ❌ mypy 会在这里报错:"Cat" is incompatible with "Quacker"
# 但如果在运行时强行跑,只有运行到 trigger_sound 内部才会抛错
trigger_sound(Cat())