Python工具包API设计:多途经点支持下采用坐标元组列表作为参数是否符合Python风格?
cartons路由API多途经点设计的疑问 我开发了一款名为cartons的Python工具包,封装了RoutingPy与OSRM库。目前路由API的调用形式是:cartons.route(lon1, lat1, lon2, lat2)。现在规划1.2.0版本迭代,想给这个API新增多途经点支持,考虑把API调整成接收坐标元组列表的形式:cartons.route(coords_lon_lat),其中coords_lon_lat是类似[(lon1, lat1), (lon2, lat2), (lon3, lat3)]的元组列表。想请教这个设计方案是不是优质且符合Python风格(Pythonic)的选择,有没有更优的API结构设计方案?
附GitHub仓库示例代码:routing.py
from routingpy import OSRM def route(base_url, coords_lon_lat:list, transport): router = OSRM(base_url=base_url) route = router.directions( overview = "full", profile = transport, locations = coords_lon_lat ) return route
首先要给你的初始方案点个赞——这个设计完全符合Python风格!用列表包裹元组的方式传递多组坐标,既直观又贴合Python处理结构化数据的习惯,而且和你底层调用的router.directions的locations参数格式完全对齐,省去了额外的格式转换,代码简洁易维护。
不过,为了让API更健壮、易用,这里还有几个优化方向可以参考:
1. 兼容旧API,实现平滑过渡
如果你的工具包已经有用户在使用,直接修改API签名会导致老代码报错。可以通过参数判断来兼容两种调用方式:
from routingpy import OSRM from typing import List, Tuple, Union def route(base_url: str, coords: Union[List[Tuple[float, float]], float], *args, transport: str): # 处理两种输入格式 if isinstance(coords, list): coords_lon_lat = coords else: # 旧格式:lon1, lat1, lon2, lat2... if len(args) < 3 or (len(args) + 1) % 2 != 0: raise ValueError("For legacy format, provide pairs of lon/lat: lon1, lat1, lon2, lat2...") coords_lon_lat = [(coords, args[0])] + list(zip(args[1::2], args[2::2])) # 参数验证 if len(coords_lon_lat) < 2: raise ValueError("At least two coordinates (start and end) are required.") router = OSRM(base_url=base_url) return router.directions( overview="full", profile=transport, locations=coords_lon_lat )
这样老用户的cartons.route(base_url, lon1, lat1, lon2, lat2, transport="driving")依然能用,新用户则可以用cartons.route(base_url, [(lon1, lat1), (lon2, lat2), (lon3, lat3)], transport="driving"),兼顾了兼容性和新功能。
2. 完善类型提示,提升开发体验
给参数加上更精确的类型提示,能让IDE自动补全和错误检查,这也是现代Python的最佳实践之一。比如把coords_lon_lat:list改成coords_lon_lat: List[Tuple[float, float]],明确告诉用户每个元素是包含两个数值的元组。
3. 增加参数验证,提前规避错误
在函数开头对输入的坐标列表做简单验证,比如检查列表长度至少为2(必须有起点和终点)、每个元组都是数值类型,这样能提前给出清晰的错误提示,而不是让底层库抛出模糊的异常,提升用户排查问题的效率:
def route(base_url: str, coords_lon_lat: List[Tuple[float, float]], transport: str): # 验证坐标列表长度 if len(coords_lon_lat) < 2: raise ValueError("At least two coordinates (start and end) are required.") # 验证每个坐标的格式 for idx, (lon, lat) in enumerate(coords_lon_lat): if not isinstance(lon, (int, float)) or not isinstance(lat, (int, float)): raise TypeError(f"Coordinate at index {idx} must be a tuple of numbers, got ({type(lon).__name__}, {type(lat).__name__})") # 后续逻辑...
4. 把可选参数设为关键字参数
像transport、overview这类可选参数,建议设为带默认值的关键字参数,这样用户调用时不用记忆参数顺序,也能按需调整:
def route(base_url: str, coords_lon_lat: List[Tuple[float, float]], transport: str = "driving", overview: str = "full"): router = OSRM(base_url=base_url) return router.directions( overview=overview, profile=transport, locations=coords_lon_lat )
用户可以根据需要灵活传递参数:cartons.route(my_base_url, my_coords, transport="walking")或者cartons.route(my_base_url, my_coords, overview="simplified")。
总结
你的初始设计是非常Pythonic的核心思路,在此基础上加上兼容性处理、类型提示和参数验证,就能打造出一个既易用又健壮的API。如果团队内部使用或者用户群体比较新,也可以直接采用新的列表格式,简化API的复杂度——具体要看你的用户场景和需求哦。
内容的提问来源于stack exchange,提问作者Andrej Bajusic

