0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Python裝飾器如何增加任務(wù)超時(shí)退出功能

科技綠洲 ? 來(lái)源:Python實(shí)用寶典 ? 作者:Python實(shí)用寶典 ? 2023-11-01 10:53 ? 次閱讀

任務(wù)超時(shí)退出

我們?nèi)粘T谑褂玫母鞣N網(wǎng)絡(luò)請(qǐng)求庫(kù)時(shí)都帶有timeout參數(shù),例如request庫(kù)。這個(gè)參數(shù)可以使請(qǐng)求超時(shí)就不再繼續(xù)了,直接拋出超時(shí)錯(cuò)誤,避免等太久。

如果我們自己開(kāi)發(fā)的方法也希望增加這個(gè)功能,該如何做呢?

方法很多,但最簡(jiǎn)單直接的是使用并發(fā)庫(kù)futures,為了使用方便,我將其封裝成了一個(gè)裝飾器,代碼如下:

import functools
from concurrent import futures

executor = futures.ThreadPoolExecutor(1)

def timeout(seconds):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            future = executor.submit(func, *args, **kw)
            return future.result(timeout=seconds)
        return wrapper
    return decorator

定義了以上函數(shù),我們就有了一個(gè)超時(shí)結(jié)束的裝飾器,下面可以測(cè)試一下:

import time

@timeout(1)
def task(a, b):
    time.sleep(1.2)
    return a+b

task(2, 3)

結(jié)果:

---------------------------------------------------------------------------
TimeoutError                              Traceback (most recent call last)
...
D:Anaconda3libconcurrentfutures_base.py in result(self, timeout)
    432                 return self.__get_result()
    433             else:
-- > 434                 raise TimeoutError()
    435 
    436     def exception(self, timeout=None):

TimeoutError:

上面我們通過(guò)裝飾器定義了函數(shù)的超時(shí)時(shí)間為1秒,通過(guò)睡眠模擬函數(shù)執(zhí)行超過(guò)1秒時(shí),成功的拋出了超時(shí)異常。

程序能夠在超時(shí)時(shí)間內(nèi)完成時(shí):

@timeout(1)
def task(a, b):
    time.sleep(0.9)
    return a+b

task(2, 3)

結(jié)果:

5

可以看到,順利的得到了結(jié)果。

這樣我們就可以通過(guò)一個(gè)裝飾器給任何函數(shù)增加超時(shí)時(shí)間,這個(gè)函數(shù)在規(guī)定時(shí)間內(nèi)還處理不完就可以直接結(jié)束任務(wù)。

前面我將這個(gè)裝飾器將所需的變量定義到了外部,其實(shí)我們還可以通過(guò)類裝飾器進(jìn)一步封裝,代碼如下:

import functools
from concurrent import futures

class timeout:
    __executor = futures.ThreadPoolExecutor(1)

    def __init__(self, seconds):
        self.seconds = seconds

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            future = timeout.__executor.submit(func, *args, **kw)
            return future.result(timeout=self.seconds)
        return wrapper

經(jīng)測(cè)試使用類裝飾器能得到同樣的效果。

注意:使用@functools.wraps的目的是因?yàn)楸谎b飾的func函數(shù)元信息會(huì)被替換為wrapper函數(shù)的元信息,而@functools.wraps(func)將wrapper函數(shù)的元信息替換為func函數(shù)的元信息。最終雖然返回的是wrapper函數(shù),元信息卻依然是原有的func函數(shù)。

在函數(shù)式編程中,函數(shù)的返回值是函數(shù)對(duì)象被稱為閉包。

日志記錄

如果我們需要記錄部分函數(shù)的執(zhí)行時(shí)間,函數(shù)執(zhí)行前后打印一些日志,裝飾器是一種很方便的選擇。

代碼如下:

import time
import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        res = func(*args, **kwargs)
        end = time.perf_counter()
        print(f'函數(shù) {func.__name__} 耗時(shí) {(end - start) * 1000} ms')
        return res
    return wrapper

裝飾器 log 記錄某個(gè)函數(shù)的運(yùn)行時(shí)間,并返回其執(zhí)行結(jié)果。

測(cè)試一下:

@log
def now():
    print('2021-7-1')

now()

結(jié)果:

2021-7-1
函數(shù) now 耗時(shí) 0.09933599994838005 ms

緩存

如果經(jīng)常調(diào)用一個(gè)函數(shù),而且參數(shù)經(jīng)常會(huì)產(chǎn)生重復(fù),如果把結(jié)果緩存起來(lái),下次調(diào)用同樣參數(shù)時(shí)就會(huì)節(jié)省處理時(shí)間。

定義函數(shù):

import math
import random
import time


def task(x):
    time.sleep(0.01)
    return round(math.log(x**3 / 15), 4)

執(zhí)行:

%%time
for i in range(500):
    task(random.randrange(5, 10))

結(jié)果:

Wall time: 5.01 s

此時(shí)如果我們使用緩存的效果就會(huì)大不一樣,實(shí)現(xiàn)緩存的裝飾器有很多,我就不重復(fù)造輪子了,這里使用functools包下的LRU緩存:

from functools import lru_cache

@lru_cache()
def task(x):
    time.sleep(0.01)
    return round(math.log(x**3 / 15), 4)

執(zhí)行:

%%time
for i in range(500):
    task(random.randrange(5, 10))

結(jié)果:

Wall time: 50 ms

約束某個(gè)函數(shù)的可執(zhí)行次數(shù)

如果我們希望程序中的某個(gè)函數(shù)在整個(gè)程序的生命周期中只執(zhí)行一次或N次,可以寫(xiě)一個(gè)這樣的裝飾器:

import functools


class allow_count:
    def __init__(self, count):
        self.count = count
        self.i = 0

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            if self.i >= self.count:
                return
            self.i += 1
            return func(*args, **kw)
        return wrapper

測(cè)試:

@allow_count(3)
def job(x):
    x += 1
    return x


for i in range(5):
    print(job(i))

結(jié)果:

1
2
3
None
None
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 參數(shù)
    +關(guān)注

    關(guān)注

    11

    文章

    1757

    瀏覽量

    32044
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4723

    瀏覽量

    68236
  • python
    +關(guān)注

    關(guān)注

    55

    文章

    4768

    瀏覽量

    84376
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    python學(xué)習(xí):三個(gè)測(cè)試庫(kù)的裝飾實(shí)現(xiàn)思路

    Python 中實(shí)現(xiàn)參數(shù)化測(cè)試的幾個(gè)庫(kù),并留下一個(gè)問(wèn)題: 它們是如何做到把一個(gè)方法變成多個(gè)方法,并且將每個(gè)方法與相應(yīng)的參數(shù)綁定起來(lái)的呢? 我們?cè)偬釤捯幌?,原?wèn)題等于是:在一個(gè)類中,如何使用裝飾
    的頭像 發(fā)表于 09-27 11:44 ?3103次閱讀
    <b class='flag-5'>python</b>學(xué)習(xí):三個(gè)測(cè)試庫(kù)的<b class='flag-5'>裝飾</b><b class='flag-5'>器</b>實(shí)現(xiàn)思路

    理解Python裝飾及其工作原理

    Python 是一種對(duì)新手很友好的語(yǔ)言。但是,它也有很多較難掌握的高級(jí)功能,比如裝飾(decorator)。很多初學(xué)者一直不理解裝飾
    發(fā)表于 10-08 11:39 ?2195次閱讀

    分享python 7個(gè)好用的裝飾

    ): return x + y4、deprecated這個(gè)相信大家在使用別的包時(shí)都遇到過(guò),當(dāng)要下線一個(gè)老版本的函數(shù)的時(shí)候就可以使用這個(gè)裝飾。安裝:pip install Deprecated
    發(fā)表于 06-15 16:54

    如何實(shí)現(xiàn)事件結(jié)構(gòu)中存在多個(gè)超時(shí)功能

    實(shí)現(xiàn)事件結(jié)構(gòu)中存在多個(gè)超時(shí)功能
    發(fā)表于 04-03 14:57 ?2次下載

    一文讀懂Python裝飾

    裝飾前,還要先要明白一件事,Python 中的函數(shù)和 Java、C++不太一樣,Python 中的函數(shù)可以像普通變量一樣當(dāng)做參數(shù)傳遞給另外一個(gè)函數(shù)。
    發(fā)表于 04-28 10:48 ?3414次閱讀
    一文讀懂<b class='flag-5'>Python</b><b class='flag-5'>裝飾</b><b class='flag-5'>器</b>

    讓你學(xué)寫(xiě)Python裝飾的五大理由

    你必須學(xué)寫(xiě)Python裝飾的五個(gè)理由
    的頭像 發(fā)表于 03-02 10:06 ?1877次閱讀

    Python的函數(shù)裝飾器使用方法

    Python中的裝飾是一種可以裝飾其它對(duì)象的工具,簡(jiǎn)單地說(shuō),他們是修改其他函數(shù)的功能的函數(shù)。該工具本質(zhì)上是一個(gè)可調(diào)用的對(duì)象(callabl
    的頭像 發(fā)表于 01-21 11:36 ?1554次閱讀
    <b class='flag-5'>Python</b>的函數(shù)<b class='flag-5'>裝飾</b>器使用方法

    Python裝飾的原理和案例

    Python中的裝飾器用于擴(kuò)展可調(diào)用對(duì)象的功能,而無(wú)需修改其結(jié)構(gòu)。基本上,裝飾函數(shù)包裝另一個(gè)函數(shù)以增強(qiáng)或修改其行為。我們可以通過(guò)一個(gè)具體的
    的頭像 發(fā)表于 07-01 11:35 ?2197次閱讀

    Python定時(shí)任務(wù)的實(shí)現(xiàn)方式

    在日常工作中,我們常常會(huì)用到需要周期性執(zhí)行的任務(wù),一種方式是采用 Linux 系統(tǒng)自帶的 crond 結(jié)合命令行實(shí)現(xiàn)。另外一種方式是直接使用Python。接下來(lái)整理的是常見(jiàn)的Python定時(shí)
    的頭像 發(fā)表于 10-08 15:20 ?5549次閱讀

    Python裝飾的使用

    定義 首先我們先來(lái)了解下裝飾的定義。顧名思義,在Python中,裝飾本質(zhì)上就是一個(gè)函數(shù),它可以接收一個(gè)函數(shù)作為參數(shù),然后返回一個(gè)新的函數(shù)
    的頭像 發(fā)表于 06-21 16:54 ?716次閱讀

    裝飾模式和代理模式的區(qū)別

    什么是裝飾模式 裝飾模式(Decorator Pattern): 在不改變對(duì)象自身的基礎(chǔ)上,在程序運(yùn)行期間給對(duì)象動(dòng)態(tài)的添加職責(zé); 感覺(jué)和繼承如出一轍,不改變父類,子類可拓展
    的頭像 發(fā)表于 10-08 14:25 ?3452次閱讀
    <b class='flag-5'>裝飾</b><b class='flag-5'>器</b>模式和代理模式的區(qū)別

    Python自制簡(jiǎn)單實(shí)用的日志裝飾

    在寫(xiě)代碼的時(shí)候,往往會(huì)漏掉日志這個(gè)關(guān)鍵因素,導(dǎo)致功能在使用的時(shí)候出錯(cuò)卻無(wú)法溯源。 其實(shí),只需要寫(xiě)一個(gè)非常簡(jiǎn)單的日志裝飾,我們就能大大提升排查問(wèn)題的效率。 1.簡(jiǎn)陋版裝飾
    的頭像 發(fā)表于 10-21 14:39 ?683次閱讀
    <b class='flag-5'>Python</b>自制簡(jiǎn)單實(shí)用的日志<b class='flag-5'>裝飾</b><b class='flag-5'>器</b>

    Python 自制簡(jiǎn)單實(shí)用的日志裝飾

    在寫(xiě)代碼的時(shí)候,往往會(huì)漏掉日志這個(gè)關(guān)鍵因素,導(dǎo)致功能在使用的時(shí)候出錯(cuò)卻無(wú)法溯源。 其實(shí),只需要寫(xiě)一個(gè)非常簡(jiǎn)單的日志裝飾,我們就能大大提升排查問(wèn)題的效率。 1.簡(jiǎn)陋版裝飾
    的頭像 發(fā)表于 10-31 15:05 ?469次閱讀
    <b class='flag-5'>Python</b> 自制簡(jiǎn)單實(shí)用的日志<b class='flag-5'>裝飾</b><b class='flag-5'>器</b>

    如何寫(xiě)一個(gè)簡(jiǎn)單的裝飾

    今天介紹的是一個(gè)已經(jīng)存在十三年,但是依舊不紅的庫(kù) decorator,好像很少有人知道他的存在一樣。 這個(gè)庫(kù)可以幫你做什么呢 ? 其實(shí)很簡(jiǎn)單,就是可以幫你更方便地寫(xiě)python裝飾代碼,更重
    的頭像 發(fā)表于 11-01 09:54 ?462次閱讀
    如何寫(xiě)一個(gè)簡(jiǎn)單的<b class='flag-5'>裝飾</b><b class='flag-5'>器</b>

    【每天學(xué)點(diǎn)AI】一個(gè)例子帶你了解Python裝飾到底在干嘛!

    今天我們來(lái)聊聊一種能給你的代碼變得“加料”的神器——Python裝飾。就像一杯咖啡,原本它是苦的,為了讓它符合我的口味,我給它添加了糖,添加之后就完美的符合了我的口味。那么,裝飾
    的頭像 發(fā)表于 09-20 16:54 ?512次閱讀
    【每天學(xué)點(diǎn)AI】一個(gè)例子帶你了解<b class='flag-5'>Python</b><b class='flag-5'>裝飾</b><b class='flag-5'>器</b>到底在干嘛!