Python 类实战:从“函数堆函数”到“客户端对象”,看类如何让 API 请求代码脱胎换骨
目录
一、从代码工作的真实痛点说起:API 客户端开发
不用类的写法:函数+全局变量,越写越“脆”
用类的写法:封装客户端与业务,代码像搭积木
第一步:定义基础 API 客户端类
第二步:扩展用户模块客户端 → 复用基类,只写业务逻辑
第三步:扩展订单模块客户端 → 同样复用基类
对比总结:类的第一个优势——封装性
二、类的第二个优势:继承复用,拒绝重复造轮子
不用类的写法:复制粘贴,改两个参数
用类的写法:继承基类,仅覆盖配置
使用测试客户端:
对比总结:类的第二个优势——继承复用
三、类的第三个优势:多态,统一调用不同模块的接口
不用类的写法:判断类型,代码臃肿
用类的写法:多态,统一接口
监控脚本简化为:
对比总结:类的第三个优势——多态性
四、类的终极优势:让代码更“工程化”
五、什么时候该用类?
六、总结:类是代码工程的“积木套装”
一、从代码工作的真实痛点说起:API 客户端开发
在后端开发或自动化测试中,调用第三方/内部 API是高频需求。假设我们要做一个 电商系统的 API 客户端,需要支持:
-
发送 GET/POST 请求;
-
自动携带认证 Token;
-
处理通用错误(如 401 重刷 Token、500 重试);
-
支持不同业务线(用户模块、订单模块)的个性化参数。
不用类的写法:函数+全局变量,越写越“脆”
新手常犯的错误是用全局变量存配置,用独立函数处理每个接口:
import requests# 全局变量存配置 → 容易被意外修改,无法区分环境
BASE_URL = "https://api.example.com"
ACCESS_TOKEN = "旧Token"# 通用请求函数 → 每个接口都要调用它,重复写参数拼接
def send_request(method: str, endpoint: str, params=None, data=None):url = f"{BASE_URL}{endpoint}"headers = {"Authorization": f"Bearer {ACCESS_TOKEN}"}try:response = requests.request(method, url, params=params, json=data, headers=headers)if response.status_code == 401:# 401 时重刷 Token → 但全局变量会被所有请求共享,可能引发竞态条件new_token = refresh_token() # 假设这是刷新 Token 的函数global ACCESS_TOKENACCESS_TOKEN = new_tokenreturn send_request(method, endpoint, params, data) # 递归重试response.raise_for_status() # 其他错误直接抛出return response.json()except Exception as e:print(f"请求失败:{e}")return None# 用户模块接口 → 每个接口都要单独写参数逻辑
def get_user(user_id: int):return send_request("GET", f"/users/{user_id}")def update_user(user_id: int, name: str):return send_request("POST", f"/users/{user_id}", data={"name": name})# 订单模块接口 → 重复造轮子
def get_order(order_id: int):return send_request("GET", f"/orders/{order_id}") # 参数拼接逻辑和 get_user 几乎一样
这段代码的问题有多致命?
-
配置脆弱:
BASE_URL和ACCESS_TOKEN是全局变量,测试时改了可能影响其他模块,线上环境切换还要手动改值; -
重复劳动:每个接口都要写
send_request+ 端点拼接,get_user和get_order逻辑高度重复; -
错误处理耦合:
send_request里的 401 重刷 Token 逻辑,一旦写错会影响所有接口,且全局 Token 可能被多个线程同时修改; -
扩展困难:新增“商品模块”时,又要复制
get_user的代码,无法复用已有逻辑。
用类的写法:封装客户端与业务,代码像搭积木
类的封装特性可以把“配置”“公共逻辑”“业务接口”绑在一起,形成独立的“API 客户端对象”。
第一步:定义基础 API 客户端类
import requestsclass BaseAPIClient:def __init__(self, base_url: str, access_token: str):self.base_url = base_url # 实例属性:每个客户端有自己的 base_urlself.access_token = access_token # 实例属性:避免全局变量污染self.session = requests.Session() # 用 Session 保持连接,提升性能self.session.headers.update({"Authorization": f"Bearer {access_token}"})# 封装通用请求逻辑 → 所有子类复用def _send_request(self, method: str, endpoint: str, params=None, data=None):url = f"{self.base_url}{endpoint}"try:response = self.session.request(method, url, params=params, json=data)if response.status_code == 401:# 401 时重刷 Token → 只修改当前实例的 token,不影响其他客户端new_token = self._refresh_token() # 子类可重写此方法实现自定义刷新逻辑self.access_token = new_tokenself.session.headers.update({"Authorization": f"Bearer {new_token}"})return self._send_request(method, endpoint, params, data) # 递归重试response.raise_for_status()return response.json()except Exception as e:print(f"[{self.__class__.__name__}] 请求失败:{e}")return None# 抽象方法:子类必须实现 Token 刷新逻辑(强制约束)def _refresh_token(self):raise NotImplementedError("子类需实现 _refresh_token 方法")
第二步:扩展用户模块客户端 → 复用基类,只写业务逻辑
class UserAPIClient(BaseAPIClient):def __init__(self, base_url: str, access_token: str):super().__init__(base_url, access_token) # 调用父类初始化,复用配置和 Session# 实现 Token 刷新逻辑 → 用户模块可能有专属刷新方式def _refresh_token(self):# 假设用户模块刷新 Token 调这个接口refresh_url = f"{self.base_url}/auth/refresh"response = self.session.post(refresh_url)new_token = response.json()["token"]return new_token# 用户相关接口 → 直接调用基类的 _send_request,无需重复写 URL 拼接def get_user(self, user_id: int):return self._send_request("GET", f"/users/{user_id}")def update_user(self, user_id: int, name: str):return self._send_request("POST", f"/users/{user_id}", data={"name": name})
第三步:扩展订单模块客户端 → 同样复用基类
class OrderAPIClient(BaseAPIClient):def __init__(self, base_url: str, access_token: str):super().__init__(base_url, access_token)# 订单模块可能用不同的 Token 刷新方式def _refresh_token(self):# 假设订单模块刷新 Token 调另一个接口refresh_url = f"{self.base_url}/order/auth/refresh"response = self.session.post(refresh_url)return response.json()["token"]def get_order(self, order_id: int):return self._send_request("GET", f"/orders/{order_id}")
对比总结:类的第一个优势——封装性
不用类时,配置和逻辑散落在全局变量和函数中,像“散落的零件”;
用类时,配置(base_url/access_token)、公共逻辑(_send_request)、业务接口(get_user/get_order)被封装在对象里,代码更“紧凑”且安全。
二、类的第二个优势:继承复用,拒绝重复造轮子
假设需求升级:需要支持测试环境和生产环境的 API 客户端,两者只有 base_url和 access_token不同,其他逻辑(如 Token 刷新、请求发送)完全一致。
不用类的写法:复制粘贴,改两个参数
# 测试环境客户端 → 几乎复制了生产环境的代码,只改了 BASE_URL 和 ACCESS_TOKEN
TEST_BASE_URL = "https://test-api.example.com"
TEST_ACCESS_TOKEN = "测试Token"def test_send_request(method, endpoint, params=None, data=None):url = f"{TEST_BASE_URL}{endpoint}"headers = {"Authorization": f"Bearer {TEST_ACCESS_TOKEN}"}# ... 重复写请求逻辑(和之前的 send_request 几乎一样)
用类的写法:继承基类,仅覆盖配置
# 测试环境客户端 → 继承 BaseAPIClient,只改初始化参数
class TestUserAPIClient(UserAPIClient):def __init__(self):# 直接传入测试环境的 base_url 和 tokensuper().__init__(base_url="https://test-api.example.com",access_token="测试Token")
使用测试客户端:
test_client = TestUserAPIClient()
test_client.get_user(123) # 自动使用测试环境的配置和逻辑
对比总结:类的第二个优势——继承复用
不用类时,环境切换要复制粘贴整个函数;
用类时,通过继承复用父类所有逻辑,仅修改初始化参数,代码量减少90%,且避免遗漏关键配置。
三、类的第三个优势:多态,统一调用不同模块的接口
假设我们需要一个监控脚本,定期调用用户和订单接口检查数据。
不用类的写法:判断类型,代码臃肿
def monitor_data(clients):for client in clients:if isinstance(client, UserAPIClient): # 判断是不是用户客户端user_info = client.get_user(123)print(f"用户信息:{user_info}")elif isinstance(client, OrderAPIClient): # 判断是不是订单客户端order_info = client.get_order(456)print(f"订单信息:{order_info}")
用类的写法:多态,统一接口
注意到 UserAPIClient和 OrderAPIClient都继承自 BaseAPIClient,且都有 get_user/get_order等业务方法。但如果我们定义一个统一的数据获取接口(比如 fetch_data),可以通过多态简化调用:
# 在基类中定义抽象方法 → 强制子类实现
class BaseAPIClient:# ... 其他代码不变 ...def fetch_data(self):raise NotImplementedError("子类需实现 fetch_data 方法")# 用户模块客户端 → 实现 fetch_data
class UserAPIClient(BaseAPIClient):def fetch_data(self):return self.get_user(123) # 具体业务逻辑# 订单模块客户端 → 实现 fetch_data
class OrderAPIClient(BaseAPIClient):def fetch_data(self):return self.get_order(456) # 具体业务逻辑
监控脚本简化为:
def monitor_data(clients):for client in clients:data = client.fetch_data() # 统一调用 fetch_data 方法print(f"{client.__class__.__name__} 数据:{data}")
对比总结:类的第三个优势——多态性
不用类时,监控不同模块要写类型判断;
用类时,通过统一接口(fetch_data)调用不同实现,代码简洁且易新增模块(比如新增商品模块,只需实现 fetch_data)。
四、类的终极优势:让代码更“工程化”
类的本质是对“客户端”这一实体的建模:
-
客户端有状态(
base_url/access_token); -
客户端有行为(发送请求、刷新 Token);
-
不同客户端(用户/订单/测试)是“客户端”的不同“变种”。
用类的方式写 API 客户端,就像在组装一台机器:
“我需要一个用户 API 客户端,它继承自基础客户端,配置测试环境的 URL 和 Token,能获取用户数据。”
而不用的类的方式,更像在拼乐高:
“我要找测试环境的 URL,找测试 Token,写请求逻辑,处理错误……”
五、什么时候该用类?
类在以下场景中能显著提升代码质量:
-
需要管理状态:比如 API 客户端的
base_url/access_token; -
需要复用公共逻辑:比如请求发送、错误处理;
-
需要环境隔离:比如测试/生产环境的不同配置;
-
需要统一接口:比如监控脚本调用不同模块的接口。
六、总结:类是代码工程的“积木套装”
类的核心不是语法,而是组织代码的思维方式:
-
用封装把数据和行为绑在一起,避免混乱;
-
用继承复用公共逻辑,拒绝重复;
-
用多态统一接口,简化调用。
回到最初的 API 客户端需求,用类后的代码:
-
更安全:配置封装在对象里,避免全局污染;
-
更易维护:修改请求逻辑只需改基类;
-
更易扩展:新增模块只需继承基类,实现业务方法。
如果你还在用“函数堆函数”写 API 客户端,不妨试试用类重构——你会发现,代码从“东拼西凑的补丁”变成了“可组装的积木”!
最后留个小练习:
试试用类实现一个“数据库连接客户端”,要求:
-
支持 MySQL 和 PostgreSQL,通过继承实现;
-
封装连接、查询、关闭连接的公共逻辑;
-
处理连接超时错误,自动重连;
-
提供统一的
execute_query方法执行 SQL。
欢迎在评论区分享你的代码~
关注我,后续会写更多 Python 面向对象的高级技巧(比如魔法方法、依赖注入、设计模式)!
