Python实用模块(二十四)tenacity

软硬件环境

  • windows 10 64bits

  • anaconda with python 3.7

  • tenacity

简介

在实际应用中,经常会碰到在web请求时,因为网络的不稳定,会有请求超时的问题,这时候,一般都是自己去实现重试请求的逻辑,直到得到响应或者超时。虽然这样的逻辑并不复杂,但是代码写起来却不那么优雅,不那么pythonic

tenacity是一个重试库,使用python语言编写,它能够让我们在任务的重试操作中变得非常简单,使用的是Apache 2.0开源协议。

tenacity有如下特性

  • 装饰器API

  • 可指定停止条件

  • 可指定等待条件

  • 自定义异常时的重试

  • 自定义特定返回值的重试

  • 在协程中使用

模块安装

使用pip安装tenacity

pip install tenacity

示例代码

无条件重试

这是tenacity最基本的用法,在task方法中使用装饰器@retry,当task出现异常时,我们就重新运行task,这里没加任何限制,如果异常一直出现,task就会一直运行下去

from tenacity import retry

@retry
def task():
    print("task running ... ")
    raise Exception

task()

执行上述代码,得到

task running ... 
task running ... 
task running ... 
task running ... 
task running ... 
task running ... 
task running ... 
task running ... 
task running ... 
task running ... 
task running ... 
task running ... 
task running ...
.
.
.
设定停止条件

通过方法stop_after_attempt指定重试的次数,如下的3次

from tenacity import retry, stop_after_attempt

@retry(stop=stop_after_attempt(3))
def task():
    print("task running ... ")
    raise Exception

task()

或者使用方法stop_after_delay指定重试多长时候后停止,如下的3秒

from tenacity import retry, stop_after_delay

@retry(stop=stop_after_delay(3))
def task():
    print("task running ... ")
    raise Exception

task()

还可以将stop_after_delaystop_after_attempt组合起来用,如下的代码,只要其中一个条件满足,task就停止运行

from tenacity import retry, stop_after_attempt, stop_after_delay

@retry(stop=(stop_after_delay(10) | stop_after_attempt(5)))
def task():
    print("task running ... ")
    raise Exception

task()
设定等待时间

使用方法wait_fixed来指定重试时等待的时间,如下代码中的3秒,每一次重试task前都要等待3秒钟

from tenacity import retry, wait_fixed

@retry(wait=wait_fixed(3))
def task():
    print("task running ... ")
    raise Exception

task()

使用方法wait_random(min, max),在minmax之间随机取值,每一次task重试前就等待这个随机值,单位是秒

from tenacity import retry, wait_random

@retry(wait=wait_random(min=1, max=3))
def task():
    print("task running ... ")
    raise Exception

task()

当然,上面2中种方法也是可以结合起来用的

from tenacity import retry, wait_fixed, wait_random

@retry(wait=wait_fixed(3) + wait_random(0, 2))
def task():
    print("task running ... ")
    raise Exception

task()
什么情况下重试

可以通过retry_if_exception_type指定特定类型的异常出现时,任务才重试

from tenacity import retry, retry_if_exception_type

@retry(retry=retry_if_exception_type(IOError))
def task():
    print("task running ... ")
    raise Exception

task()
重试错误后的异常抛出

出现异常后,会进行重试,若重试后还是失败,默认情况下,往上抛出的异常会变成RetryError,而不是最根本的原因。因此可以加一个参数reraise=True,使得当重试失败后,往外抛出的异常还是原来的那个异常。

from tenacity import retry, stop_after_attempt

@retry(stop=stop_after_attempt(3), reraise=True)
def task():
    print("task running ... ")
    raise Exception

task()

tenacity

在重试前执行动作

tenacity可以在任务重试前后执行某些动作,这里以加日志为例

from tenacity import retry, stop_after_attempt, before_log
import logging
import sys

logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
logger = logging.getLogger(__name__)

@retry(stop=stop_after_attempt(3), before=before_log(logger=logger, log_level=logging.DEBUG))
def task():
    print("task running ... ")
    raise Exception

task()

tenacity

重试后的操作类似,如下

from tenacity import retry, stop_after_attempt, after_log
import logging
import sys

logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
logger = logging.getLogger(__name__)

@retry(stop=stop_after_attempt(3), after=after_log(logger=logger, log_level=logging.DEBUG))
def task():
    print("task running ... ")
    raise Exception

task()

tenacity

Python实用模块专题

更多有用的python模块,请移步

https://xugaoxiang.com/category/python/modules/

参考资料

  • https://github.com/jd/tenacity

<think>好的,用户想要用Python编写一个国内旅游攻略自动化生成软件,集成DeepSeek API、和风天气API和高德地图API。首先,我需要理清楚用户的需求和各个API的功能。用户提到的功能流程图显示,用户输入城市名称和旅游天数后,系统需要获取景点和天气信息,智能拆分景点,关联附近美食,最后生成时间表。 首先,用户可能是一个开发者或者有一定编程基础的人,想要自动化生成旅游攻略,节省时间和精力。他们的真实需求不仅是整合多个API,还要确保整个流程顺畅,数据准确,并且生成的时间表合理实用。可能还有一些未明确的需求,比如错误处理、用户体验优化等。 接下来,我需要考虑各个API的使用方法。和风天气API用于获取天气数据,需要注册获取API key,并了解其返回的数据结构,比如是否有每日天气的daily字段,之前用户提到的KeyError可能就是因为数据结构不符,所以需要注意这点。高德地图API用于获取景点和附近美食,需要调用他们的POI搜索和周边推荐接口。DeepSeek API可能用于自然语言处理,生成旅游建议或时间表描述,但用户没有具体说明用途,可能需要进一步确认。 然后,设计整个系统的架构。用户输入城市和天数后,系统需要依次调用天气API获取天气情况,高德地图获取景点列表,然后根据天数智能拆分景点,可能要考虑景点之间的距离、开放时间、用户评分等因素。接着,为每个景点查找附近的美食,最后生成详细的时间安排表。 在智能拆分景点部分,可能需要算法来优化路线,比如基于地理位置聚类,或者使用旅行商问题(TSP)的近似解法,但考虑到时间限制,可能需要简化处理,比如按区域划分或者热门程度排序。关联附近美食需要调用高德地图的周边搜索接口,传入景点坐标和餐饮类别的关键词。 生成时间表时,需要合理分配每天的景点数量,考虑交通时间和用餐时间,可能需要预设每个景点的游览时长,并结合天气情况调整户外活动安排。 错误处理也很重要,比如API调用失败、数据解析错误、网络问题等,需要添加try-except块,友好的错误提示,并可能重试机制。同时,用户可能需要保存生成的攻略,或者导出为PDF、Excel格式,但用户没有提到,可能需要后续扩展。 代码结构方面,使用函数模块化每个步骤:获取天气、获取景点、拆分景点、查找美食、生成时间表。使用配置文件管理API密钥,避免硬编码。可能需要异步处理API请求以提高效率,但同步请求更简单,适合初步实现。 测试环节需要模拟不同城市的输入,检查各个API的响应是否正确,处理边界情况,比如旅游天数过多或过少,城市名称错误等情况。用户可能在实际使用中遇到各种问题,比如高德API的限流,需要处理HTTP状态码和响应数据中的错误码。 最后,用户可能需要一个简单的命令行界面或图形界面,但根据问题描述,可能只需要核心功能的实现。需要编写文档说明如何配置API密钥和运行程序。 总结下来,开发步骤包括:注册API获取密钥,编写各个API的调用函数,实现数据处理和逻辑整合,设计智能拆分算法,处理异常情况,测试并优化用户体验。需要确保代码的可维护性和扩展性,方便后续添加新功能或更换API服务商。</think>--- ### **国内旅游攻略自动化生成软件架构设计** 以下基于Python实现多API集成方案,使用模块化设计确保可扩展性。 --- ### **一、技术选型** | 组件 | 用途 | 官方文档 | |---------------|-------------------------------|-------------------------------------| | DeepSeek API | 生成自然语言描述的行程建议 | [DeepSeek开发者平台](https://platform.deepseek.com/) | | 和风天气API | 获取指定日期天气数据 | [和风天气开发文档](https://dev.qweather.com/) | | 高德地图API | 获取景点POI/周边美食/路线规划 | [高德开放平台](https://lbs.amap.com/) | --- ### **二、核心功能实现代码** #### **1. 基础配置** ```python import requests import json from datetime import datetime, timedelta # API配置 CONFIG = { "qweather": { "key": "YOUR_QWEATHER_KEY", "daily_url": "https://api.qweather.com/v7/weather/7d" }, "amap": { "key": "YOUR_AMAP_KEY", "poi_url": "https://restapi.amap.com/v3/place/text", "around_url": "https://restapi.amap.com/v3/place/around" }, "deepseek": { "key": "YOUR_DEEPSEEK_KEY", "api_url": "https://api.deepseek.com/v1/chat/completions" } } ``` #### **2. 天气获取模块** ```python def get_weather(city: str, date: str) -> dict: """ 获取指定城市日期的天气数据 返回格式:{"date": "2024-05-20", "temp": "20~28℃", "weather": "晴"} """ params = { "location": get_city_code(city), # 需实现城市名转adcode "key": CONFIG["qweather"]["key"] } try: response = requests.get(CONFIG["qweather"]["daily_url"], params=params) data = response.json() if "daily" not in data: raise ValueError("API返回数据异常") for day_data in data["daily"]: if day_data["fxDate"] == date: return { "date": date, "temp": f"{day_data['tempMin']}~{day_data['tempMax']}℃", "weather": day_data["textDay"] } return {"error": "超出预报范围"} except Exception as e: return {"error": str(e)} ``` #### **3. 景点与美食获取模块** ```python def get_attractions(city: str, max_num=20) -> list: """获取城市热门景点列表""" params = { "key": CONFIG["amap"]["key"], "keywords": "景点", "city": city, "types": "风景名胜", "output": "json" } response = requests.get(CONFIG["amap"]["poi_url"], params=params) return sorted( response.json()["pois"], key=lambda x: float(x["biz_ext"]["rating"] or 0), reverse=True )[:max_num] def find_nearby_food(location: str, radius=1000) -> list: """根据坐标查找周边美食(高德坐标系)""" params = { "key": CONFIG["amap"]["key"], "location": location, "radius": radius, "types": "餐饮", "sortrule": "distance" } response = requests.get(CONFIG["amap"]["around_url"], params=params) return response.json()["pois"] ``` #### **4. 智能行程生成模块** ```python def generate_itinerary(spots: list, days: int) -> list: """ 景点智能拆分算法 输入:排序后的景点列表、旅游天数 输出:按天分组的景点集合 """ # 基础版本:简单均分(后续可升级为基于距离聚类) avg = len(spots) // days return [spots[i*avg : (i+1)*avg] for i in range(days)] ``` #### **5. DeepSeek AI增强模块** ```python def generate_description(itinerary: list) -> str: """使用DeepSeek生成自然语言描述""" headers = { "Authorization": f"Bearer {CONFIG['deepseek']['key']}", "Content-Type": "application/json" } prompt = f"请将以下行程转化为旅游攻略:{json.dumps(itinerary)}" payload = { "model": "deepseek-chat", "messages": [{"role": "user", "content": prompt}] } response = requests.post(CONFIG["deepseek"]["api_url"], headers=headers, json=payload) return response.json()["choices"][0]["message"]["content"] ``` --- ### **三、主程序工作流** ```python def main(): # 用户输入 city = input("请输入旅游城市:") days = int(input("请输入旅游天数:")) start_date = input("请输入出发日期(YYYY-MM-DD):") # 获取基础数据 weather_data = [get_weather(city, (datetime.strptime(start_date, "%Y-%m-%d") + timedelta(days=i)).strftime("%Y-%m-%d")) for i in range(days)] spots = get_attractions(city) # 生成行程 daily_spots = generate_itinerary(spots, days) # 关联美食 full_itinerary = [] for day_idx, spots_group in enumerate(daily_spots): day_plan = { "date": weather_data[day_idx]["date"], "weather": weather_data[day_idx]["weather"], "spots": [] } for spot in spots_group: location = spot["location"].split(",") foods = find_nearby_food(f"{location[0]},{location[1]}") day_plan["spots"].append({ "name": spot["name"], "address": spot["address"], "recommend_foods": [food["name"] for food in foods[:3]] }) full_itinerary.append(day_plan) # AI生成描述 print(generate_description(full_itinerary)) if __name__ == "__main__": main() ``` --- ### **四、关键优化方向** 1. **路线智能优化** ```python # 使用高德路径规划API计算景点间距离 def get_route_time(origin: str, destination: str) -> float: params = { "key": CONFIG["amap"]["key"], "origin": origin, "destination": destination, "strategy": "2" # 最短距离策略 } response = requests.get("https://restapi.amap.com/v3/direction/driving", params=params) return float(response.json()["route"]["paths"][0]["duration"])/60 # 返回分钟数 ``` 2. **负载均衡设计** ```python # 动态调整每日景点数量 def dynamic_schedule(spots: list, days: int) -> list: # 根据景点评分+预计游览时间进行加权计算 weighted_spots = [] for spot in spots: time = 2.0 if "公园" in spot["type"] else 1.5 # 基础时间预估 weighted_spots.append( (spot, float(spot["biz_ext"]["rating"])*0.7 + time*0.3) ) # 使用背包算法进行分配 # ...(具体实现代码) ``` 3. **异常处理增强** ```python # 在API调用处添加重试机制 from tenacity import retry, stop_after_attempt, wait_fixed @retry(stop=stop_after_attempt(3), wait=wait_fixed(1)) def safe_api_call(url: str, params: dict): response = requests.get(url, params=params, timeout=5) response.raise_for_status() return response.json() ``` --- ### **五、部署与扩展建议** 1. **前端界面**:使用PyQt/Gradio快速搭建操作界面 2. **数据缓存**:对天气/景点数据建立本地缓存(SQLite) 3. **输出格式**:支持生成PDF/Excel文件 4. **实时更新**:添加微信/邮件通知功能 建议先从核心功能入手,逐步迭代优化。实际开发中需特别注意各API的调用频率限制,建议添加使用量监控模块
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷途小书童的Note

请博主喝矿泉书!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值