任務(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ò)誤,避免等太久。
如果我們自己開發(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次,可以寫一個(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
-
參數(shù)
+關(guān)注
關(guān)注
11文章
1860瀏覽量
32449 -
代碼
+關(guān)注
關(guān)注
30文章
4837瀏覽量
69128 -
python
+關(guān)注
關(guān)注
56文章
4811瀏覽量
85076
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
python學(xué)習(xí):三個(gè)測(cè)試庫(kù)的裝飾器實(shí)現(xiàn)思路
![<b class='flag-5'>python</b>學(xué)習(xí):三個(gè)測(cè)試庫(kù)的<b class='flag-5'>裝飾</b><b class='flag-5'>器</b>實(shí)現(xiàn)思路](https://file.elecfans.com/web1/M00/C8/4F/pIYBAF9t9KGAShWzAAC6KXDd5Nw547.jpg)
理解Python裝飾器及其工作原理
分享python 7個(gè)好用的裝飾器
如何實(shí)現(xiàn)事件結(jié)構(gòu)中存在多個(gè)超時(shí)的功能
一文讀懂Python裝飾器
![一文讀懂<b class='flag-5'>Python</b><b class='flag-5'>裝飾</b><b class='flag-5'>器</b>](https://file.elecfans.com/web1/M00/4F/D5/pIYBAFrj4EuAEhlAAAALjlIVxMg863.jpg)
Python的函數(shù)裝飾器使用方法
![<b class='flag-5'>Python</b>的函數(shù)<b class='flag-5'>裝飾</b>器使用方法](https://file.elecfans.com/web1/M00/B2/C9/pIYBAF4RXBWAO372AABsB1BAQnk864.jpg)
Python:裝飾器的原理和案例
Python定時(shí)任務(wù)的實(shí)現(xiàn)方式
在Python中裝飾器的使用
裝飾器模式和代理模式的區(qū)別
![<b class='flag-5'>裝飾</b><b class='flag-5'>器</b>模式和代理模式的區(qū)別](https://file1.elecfans.com/web2/M00/A7/30/wKgaomUiShOAALHtAAAfJ3gsn24606.jpg)
Python自制簡(jiǎn)單實(shí)用的日志裝飾器
![<b class='flag-5'>Python</b>自制簡(jiǎn)單實(shí)用的日志<b class='flag-5'>裝飾</b><b class='flag-5'>器</b>](https://file1.elecfans.com/web2/M00/A9/AF/wKgaomUzccmAOpWHAAHSmpKcgws791.jpg)
Python 自制簡(jiǎn)單實(shí)用的日志裝飾器
![<b class='flag-5'>Python</b> 自制簡(jiǎn)單實(shí)用的日志<b class='flag-5'>裝飾</b><b class='flag-5'>器</b>](https://file1.elecfans.com/web2/M00/A9/AF/wKgaomUzccmAOpWHAAHSmpKcgws791.jpg)
如何寫一個(gè)簡(jiǎn)單的裝飾器
![如何寫一個(gè)簡(jiǎn)單的<b class='flag-5'>裝飾</b><b class='flag-5'>器</b>](https://file1.elecfans.com/web2/M00/AD/83/wKgZomVBr5iAQjrSAAKqQEyQzBY477.jpg)
【每天學(xué)點(diǎn)AI】一個(gè)例子帶你了解Python裝飾器到底在干嘛!
![【每天學(xué)點(diǎn)AI】一個(gè)例子帶你了解<b class='flag-5'>Python</b><b class='flag-5'>裝飾</b><b class='flag-5'>器</b>到底在干嘛!](https://file1.elecfans.com/web2/M00/08/0A/wKgaombtODuAOOmCAAAo9ZITeps802.png)
評(píng)論