最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python使用Tenacity一行代碼實現(xiàn)自動重試詳解

 更新時間:2025年08月01日 15:34:50   作者:mortimer  
tenacity 是一個專為 Python 設(shè)計的通用重試庫,它的核心理念就是用簡單、清晰的方式,為任何可能失敗的操作添加重試能力,下面我們就來看看如何一行代碼實現(xiàn)自動重試吧

在與AI大模型API服務(wù)交互時,我們總會面對一個無法回避的現(xiàn)實:網(wǎng)絡(luò)并不總是可靠。代理可能中斷,API會限制請求頻率,連接可能超時,甚至網(wǎng)絡(luò)會短暫中斷。幸運的是,這些問題通常是暫時的。如果第一次請求失敗,稍等片刻再試一次,往往就能成功。

這種“再試一次”的策略,就是重試。它不是什么高深的技術(shù),卻是構(gòu)建可靠、健壯應(yīng)用程序的關(guān)鍵一環(huán)。

一切始于一個簡單的 API 調(diào)用

讓我們從一個真實場景開始:調(diào)用 AI 模型的 API 來完成字幕翻譯。一段基礎(chǔ)的代碼可能長這樣:

# 一個基本的 API 調(diào)用函數(shù)
from openai import OpenAI, APIConnectionError

def translate_text(text: str) -> str:
    message = [
        {'role': 'system', 'content': '您是一名頂級的字幕翻譯引擎。'},
        {'role': 'user', 'content': f'<INPUT>{text}</INPUT>'},
    ]
    model = OpenAI(api_key="YOUR_API_KEY", base_url="...") 

    try:
        response = model.chat.completions.create(model="gpt-4o", messages=message)
        if response.choices:
            return response.choices[0].message.content.strip()
        raise RuntimeError("API未返回有效結(jié)果")
    except APIConnectionError as e:
        print(f"網(wǎng)絡(luò)連接失敗: {e}。需要重試...")
        raise # 程序在這里崩潰
    except Exception as e:
        print(f"發(fā)生其他錯誤: {e}")
        raise

這段代碼能工作,但它很“脆弱”。一旦遇到網(wǎng)絡(luò)問題,它只會打印一條消息然后崩潰。我們當(dāng)然可以手動寫一個 for 循環(huán)和 time.sleep 來實現(xiàn)重試:

# 手動實現(xiàn)重試
for attempt in range(3):
    try:
        # ... API 調(diào)用邏輯 ...
        return response.choices[0].message.content.strip()
    except APIConnectionError as e:
        print(f"第 {attempt + 1} 次嘗試失敗: {e}")
        if attempt == 2: # 檢查是否是最后一次嘗試
            raise
    # ... 對其他異常也要重復(fù)寫相似的邏輯 ...

這種方式很快就會讓代碼變得復(fù)雜和混亂。重試邏輯和業(yè)務(wù)邏輯混雜在一起,而且如果我們需要在多個地方重試,就不得不編寫大量重復(fù)、易錯的代碼。

這時,tenacity 庫就派上用場了。

Tenacity 入門:一行代碼實現(xiàn)優(yōu)雅重試

tenacity 是一個專為 Python 設(shè)計的通用重試庫。它的核心理念就是用簡單、清晰的方式,為任何可能失敗的操作添加重試能力。

安裝:pip install tenacity

我們可以用 @retry 裝飾器輕松改造上面的函數(shù):

from tenacity import retry

@retry
def translate_text(text: str) -> str:
    # ... 內(nèi)部邏輯和之前完全一樣,無需任何改動 ...

僅僅加了一行 @retry,這個函數(shù)就煥然一新?,F(xiàn)在,如果 translate_text 函數(shù)內(nèi)部拋出任何異常,tenacity 都會自動捕獲它,并立即重新調(diào)用該函數(shù)。它會一直重試,永不停止,直到函數(shù)成功返回一個值。

精細(xì)控制:讓重試按我們的意愿行事

“永遠重試”通常不是我們想要的。我們需要設(shè)定一些邊界。tenacity 提供了豐富的參數(shù)來實現(xiàn)精細(xì)的控制。

1. 設(shè)置停止條件 (stop)

我們不希望無限次地重試。最常見的需求是“最多嘗試 N 次”,這可以通過 stop_after_attempt 實現(xiàn)。

from tenacity import retry, stop_after_attempt

# 總共嘗試 3 次
@retry(stop=stop_after_attempt(3))
def translate_text(text: str) -> str:
    # ...

注意:一個重要的認(rèn)知細(xì)節(jié) stop_after_attempt(N) 指的是總共的嘗試次數(shù),而不是“重試次數(shù)”。

  • stop_after_attempt(1) 意味著:執(zhí)行 1 次,如果失敗,立即停止。它根本不會重試。
  • stop_after_attempt(3) 意味著:總共執(zhí)行 3 次,即首次嘗試 + 2 次重試。

記住這個簡單的規(guī)則:如果你希望在失敗后能額外重試 Y 次,那么你應(yīng)該設(shè)置 stop_after_attempt(Y + 1)。

我們也可以按時間來限制,比如 stop_after_delay(10) 表示“10秒后停止”。更棒的是,你可以用 | (或) 操作符將它們組合起來,哪個條件先滿足就停止。

from tenacity import stop_after_delay

# 總次數(shù)達到 5 次或總耗時超過 30 秒,就停止
@retry(stop=(stop_after_attempt(5) | stop_after_delay(30)))
def translate_text(text: str) -> str:
    # ...

2. 設(shè)置等待策略 (wait)

連續(xù)不斷地快速重試可能會壓垮服務(wù)器或達到頻率限制。在兩次重試之間加入等待是明智之舉。最簡單的是固定等待,使用 wait_fixed

from tenacity import retry, wait_fixed

# 每次重試前都等待 2 秒
@retry(wait=wait_fixed(2))
def translate_text(text: str) -> str:
    # ...

在與網(wǎng)絡(luò)服務(wù)交互時,更推薦指數(shù)退避 (wait_exponential)。它會隨著重試次數(shù)的增加,逐漸拉長等待時間(比如 2s, 4s, 8s...),能有效避免在服務(wù)高峰期造成“重試風(fēng)暴”。

from tenacity import wait_exponential

# 首次重試等 2^1=2s, 之后等 4s, 8s... 最多等到 10s
@retry(wait=wait_exponential(multiplier=1, min=2, max=10))
def translate_text(text: str) -> str:
    # ...

3. 決定何時重試 (retry)

默認(rèn)情況下,tenacity 會在遇到任何異常時都進行重試。但這并不總是對的。

比如,APIConnectionError (網(wǎng)絡(luò)問題) 或 RateLimitError (請求太頻繁) 是典型的可恢復(fù)錯誤,重試很有可能會成功。但 AuthenticationError (密鑰錯誤) 或 PermissionDeniedError (無權(quán)限) 則是致命錯誤,重試多少次都注定失敗。

我們可以通過 retry_if_not_exception_type 來告訴 tenacity 遇到某些致命錯誤時不要重試。

注意:一個常見的語法陷阱 當(dāng)指定多個異常類型時,你可能會直覺地寫成 AuthenticationError | PermissionDeniedError

# 錯誤的方式!這無法按預(yù)期工作
@retry(retry=retry_if_not_exception_type(AuthenticationError | PermissionDeniedError))

在現(xiàn)代 Python 中,A | B 創(chuàng)建的是一個 UnionType 對象,而 tenacity 的這個函數(shù)期望接收一個包含異常類型的元組 (tuple)。

正確的寫法是:

from openai import AuthenticationError, PermissionDeniedError

# 正確的方式!使用元組
@retry(retry=retry_if_not_exception_type((AuthenticationError, PermissionDeniedError)))

這個小小的括號,至關(guān)重要。

當(dāng)重試最終失敗時

如果 tenacity 在用盡所有嘗試后依然失敗,它會怎么做?默認(rèn)情況下,它會拋出一個 RetryError,其中包含了最后一次失敗時的原始異常。

但有時我們不希望程序崩潰,而是想執(zhí)行一些自定義的收尾工作,比如記錄一條詳細(xì)的錯誤日志,并返回一個友好的錯誤提示。這就是 retry_error_callback 的用武之地。

from tenacity import RetryCallState

def my_error_callback(retry_state: RetryCallState):
    # retry_state 對象包含了這次重試的所有信息
    print(f"所有 {retry_state.attempt_number} 次嘗試均失敗!")
    return "默認(rèn)的翻譯結(jié)果或錯誤提示"

@retry(stop=stop_after_attempt(3), retry_error_callback=my_error_callback)
def translate_text(text: str) -> str:
    # ...

現(xiàn)在,如果函數(shù)連續(xù)失敗 3 次,它不會拋出異常,而是會返回 my_error_callback 函數(shù)的返回值。

注意:回調(diào)函數(shù)里的一個微妙陷阱 在回調(diào)函數(shù)中,我們?nèi)绾伟踩孬@取最后一次的異常信息?

def return_last_value(retry_state: RetryCallState):
    # 危險!這會重新拋出異常!
    return "失?。? + retry_state.outcome.result()

retry_state.outcome 代表了最后一次嘗試的結(jié)果。如果那次嘗試是失敗的,調(diào)用 .result() 方法會重新拋出那個異常,導(dǎo)致我們的回調(diào)函數(shù)自身崩潰。

正確的做法是使用 .exception() 方法,它會安全地返回異常對象,而不會拋出它:

def return_last_value(retry_state: RetryCallState):
    # 安全!這只會返回異常對象
    last_exception = retry_state.outcome.exception()
    return f"經(jīng)過 {retry_state.attempt_number} 次嘗試后失敗。最后一次錯誤是: {last_exception}"

當(dāng) Tenacity 遇到面向?qū)ο?/h2>

隨著代碼庫的增長,我們通常會把邏輯封裝在類里。這時,我們會遇到兩個更深層次的問題:作用域繼承。

1. 回調(diào)函數(shù)如何訪問 self

假設(shè)我們的回調(diào)函數(shù)需要訪問類的實例變量(比如 self.name)。我們可能會很自然地這樣寫:

class TTS:
    def __init__(self, name):
        self.name = name

    def _my_callback(self, retry_state):
        print(f"實例 {self.name} 的任務(wù)失敗了。")
        # ...

    # 這會失?。ameError: name 'self' is not defined
    @retry(retry_error_callback=self._my_callback)
    def run(self):
        # ...

這會立即報錯,因為 @retry 裝飾器是在定義類的時候執(zhí)行的,那時還沒有任何類的實例,自然也就沒有 self

最優(yōu)雅的解決方案是**“內(nèi)部函數(shù)閉包”**模式。我們將裝飾器應(yīng)用在一個定義于實例方法內(nèi)部的函數(shù)上:

class TTS:
    def __init__(self, name):
        self.name = name

    def run(self):
        # 在這里,self 是可用的!
        @retry(
            # 因為裝飾器在 run 方法內(nèi)部,它可以“捕獲”到 self
            retry_error_callback=self._my_callback
        )
        def _execute_task():
            # 這里是真正需要重試的邏輯
            print(f"正在為 {self.name} 執(zhí)行任務(wù)...")
            raise ValueError("任務(wù)失敗")

        # 調(diào)用被裝飾的內(nèi)部函數(shù)
        return _execute_task()

    def _my_callback(self, retry_state: RetryCallState):
        # ...

這是一種非常強大且 Pythonic 的模式,完美解決了作用域問題。

2. 如何在父類中定義重試策略,讓所有子類繼承

這是我們討論的最后一個,也是最體現(xiàn)設(shè)計思想的問題。假設(shè)我們有一個 BaseProvider 父類,和多個 MyProviderA, MyProviderB 子類。我們希望所有子類都遵循統(tǒng)一的重試規(guī)則。

一個常見的錯誤想法是在父類的空方法上應(yīng)用裝飾器。當(dāng)子類重寫該方法時,父類上的裝飾器也隨之丟失。

正確的解決方案是模板方法設(shè)計模式 (Template Method Pattern)

  • 父類定義一個模板方法 (_exec),它包含了不可變的算法框架(即我們的重試邏輯)。
  • 這個模板方法會調(diào)用一個抽象的鉤子方法 (_do_work)。
  • 子類只需要實現(xiàn)這個鉤子方法,填充具體的業(yè)務(wù)邏輯即可。

讓我們用一個更完整的例子來構(gòu)建這個模式:

from openai import OpenAI, AuthenticationError, PermissionDeniedError
from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_not_exception_type, RetryCallState

# 1. 定義一個通用的、可復(fù)用的異常處理類
class RetryRaise:
    # 定義不應(yīng)重試的致命異常
    NO_RETRY_EXCEPT = (AuthenticationError, PermissionDeniedError)

    @classmethod
    def _raise(cls, retry_state: RetryCallState):
        ex = retry_state.outcome.exception()
        if ex:
            # 根據(jù)不同異常類型,進行日志記錄并拋出自定義的、更友好的 RuntimeError
            # ... 此處可以添加更復(fù)雜的異常分類邏輯 ...
            raise RuntimeError(f"重試 {retry_state.attempt_number} 次后最終失敗: {ex}") from ex
        raise RuntimeError(f"重試 {retry_state.attempt_number} 次后失敗,但未捕獲到異常。")

# 2. 實現(xiàn)模板父類
class BaseProvider:
    @retry(
        stop=stop_after_attempt(3),
        wait=wait_fixed(2),
        retry=retry_if_not_exception_type(RetryRaise.NO_RETRY_EXCEPT),
        retry_error_callback=RetryRaise._raise
    )
    def _exec(self) -> str:
        """這是模板方法,負(fù)責(zé)重試。子類不應(yīng)重寫它。"""
        # 調(diào)用鉤子方法,由子類實現(xiàn)
        return self._do_work()

    def _do_work(self) -> str:
        """這是鉤子方法,子類必須實現(xiàn)它。"""
        raise NotImplementedError("子類必須實現(xiàn) _do_work 方法")

# 3. 實現(xiàn)具體的子類
class DeepSeekProvider(BaseProvider):
    def __init__(self, api_key: str, base_url: str):
        self.api_key = api_key
        self.base_url = base_url
        self.model = OpenAI(api_key=self.api_key, base_url=self.base_url)

    def _do_work(self) -> str:
        """這里只關(guān)心核心業(yè)務(wù)邏輯,完全不用考慮重試。"""
        response = self.model.chat.completions.create(
            model="deepseek-chat",
            messages=[{'role': 'user', 'content': '你是誰?'}]
        )
        if response.choices:
            return response.choices[0].message.content.strip()
        raise RuntimeError(f"API未返回有效結(jié)果: {response}")

# --- 如何使用 ---
 provider = DeepSeekProvider(api_key="...", base_url="...")
 try:
     # 我們調(diào)用的是 _exec,它包含了重試邏輯
     result = provider._exec()
     print("執(zhí)行成功:", result)
 except RuntimeError as e:
     # 如果最終失敗,會捕獲到 RetryRaise 拋出的友好異常
     print("執(zhí)行失敗:", e)

通過這種方式,我們將重試策略(不變的部分)和業(yè)務(wù)邏輯(可變的部分)完美地分離開來,構(gòu)建了一個既健壯又易于擴展的框架。

tenacity 是一個看似簡單,實則功能強大的庫。它不僅能輕松應(yīng)對簡單的重試場景,更能通過巧妙的設(shè)計模式,解決復(fù)雜的、面向?qū)ο蟮膽?yīng)用程序中的可靠性問題。

到此這篇關(guān)于Python使用Tenacity一行代碼實現(xiàn)自動重試詳解的文章就介紹到這了,更多相關(guān)Python Tenacity自動重試內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Python爬蟲實戰(zhàn)之虎牙視頻爬取附源碼

    Python爬蟲實戰(zhàn)之虎牙視頻爬取附源碼

    讀萬卷書不如行萬里路,學(xué)的扎不扎實要通過實戰(zhàn)才能看出來,本篇文章手把手帶你爬取虎牙短視頻數(shù)據(jù),大家可以在實戰(zhàn)過程中查缺補漏,加深學(xué)習(xí)
    2021-10-10
  • python寫個喚醒睡眠電腦的腳本

    python寫個喚醒睡眠電腦的腳本

    這篇文章主要為大家詳細(xì)介紹了如何使用python寫個喚醒睡眠電腦的腳本,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-01-01
  • python使用箱型圖剔除異常值的實現(xiàn)方法

    python使用箱型圖剔除異常值的實現(xiàn)方法

    python中的箱線圖可用于分析數(shù)據(jù)中的異常值,下面這篇文章主要給大家介紹了關(guān)于python使用箱型圖剔除異常值的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-05-05
  • 基于python的MD5腳本開發(fā)思路

    基于python的MD5腳本開發(fā)思路

    這篇文章主要介紹了基于python的MD5腳本,通過 string模塊自動生成字典,使用permutations()函數(shù),對字典進行全排列,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-03-03
  • Python分析和處理excel文件數(shù)據(jù)的詳細(xì)步驟

    Python分析和處理excel文件數(shù)據(jù)的詳細(xì)步驟

    Python 提供了多種工具來分析和處理 Excel 文件數(shù)據(jù),最常用的庫包括 pandas、openpyxl 和 xlrd,本文將詳細(xì)介紹如何使用這些工具讀取、處理和分析 Excel 文件中的數(shù)據(jù),需要的朋友可以參考下
    2025-05-05
  • Python之用戶輸入的實例

    Python之用戶輸入的實例

    今天小編就為大家分享一篇Python之用戶輸入的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-06-06
  • python的Crypto模塊實現(xiàn)AES加密實例代碼

    python的Crypto模塊實現(xiàn)AES加密實例代碼

    這篇文章主要介紹了python的Crypto模塊實現(xiàn)AES加密實例代碼,簡單介紹了實現(xiàn)步驟,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • pandas DataFrame where的實現(xiàn)示例

    pandas DataFrame where的實現(xiàn)示例

    pandas.DataFrame.where方法提供了一種靈活的方式來根據(jù)條件篩選和替換DataFrame中的元素,本文主要介紹了pandas DataFrame where的實現(xiàn)示例,感興趣的可以了解一下
    2025-04-04
  • Django實現(xiàn)組合搜索的方法示例

    Django實現(xiàn)組合搜索的方法示例

    本篇文章主要介紹了Django實現(xiàn)組合搜索的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • 對python 中re.sub,replace(),strip()的區(qū)別詳解

    對python 中re.sub,replace(),strip()的區(qū)別詳解

    今天小編就為大家分享一篇對python 中re.sub,replace(),strip()的區(qū)別詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-07-07

最新評論

靖州| 克东县| 遂川县| 资阳市| 廊坊市| 马龙县| 柘城县| 大姚县| 丰台区| 江山市| 阜新市| 武定县| 晋江市| 德惠市| 手游| 平江县| 南投县| 娱乐| 西乡县| 湟源县| 乾安县| 兰州市| 浮梁县| 左贡县| 辽阳县| 株洲县| 平罗县| 宁海县| 佛坪县| 石河子市| 惠东县| 遂溪县| 聂荣县| 天祝| 南靖县| 都兰县| 阳城县| 新乐市| 托克托县| 鄂尔多斯市| 辛集市|