Python的自定义超时机制——装饰器的妙用

装饰器

关于装饰器的入门,可以参考这篇文章:12步轻松搞定python装饰器
简单来说,装饰器其实就是一个闭包(闭包 – 被函数记住的封闭作用域 – 能够被用来创建自定义的函数),把一个函数当做参数然后返回一个替代版的函数,在Python里面,函数也是对象。实际上装饰器就是一个加强的函数,目的是为了更简洁地加强函数的同时并且不影响函数的内部。
那么我们想实现超时中止机制的时候,是不是可以考虑给某函数func增加一个装饰器,让装饰器实现超时中止的功能,这样还能把装饰器用在其他的函数里面,同时我们的func函数的内部也不会有额外的代码。
有空会补充装饰器的细节,或者另开一篇文章。

信号

在实现超时中止的装饰器之前,先引入一下Python的信号(signal)机制。
信号机制的作用:发送和接收异步系统信号
信号是一个操作系统特性,它提供了一个途径可以通知程序发生了一个事件并异步处理这个事件。信号可以由系统本身生成,也可以从一个进程发送到另一个进程。
信号实际上是进程之间通讯的方式,是一种软件中断。一个进程一旦接收到信号就会打断原来的程序执行流程来处理信号。
Python标准库中的signal包负责在Python程序内部处理信号,典型的操作包括预设信号处理函数,暂停并等待信号,以及定时发出SIGALRM等。要注意,signal包主要是针对UNIX平台(比如Linux, MAC OS),而Windows内核中由于对信号机制的支持不充分,所以在Windows上的Python不能发挥信号系统的功能。
下面简单介绍一下这次会用到的signal包的相关方法,更多的细节可以参考:

signal包的核心是使用signal.signal()函数来预设(register)信号处理函数,如下所示:

import signal
'''
python 的信号名与Linux系统一致,
查看你的linux支持哪些信号:kill -l 即可
SIGINT 终止进程 中断进程 (control+c) 
SIGTERM 终止进程 软件终止信号 
SIGKILL 终止进程 杀死进程 
SIGALRM 闹钟信号
'''
signal.signal(signal_num, handler)
'''
signal_num为某个信号,handler为该信号的处理函数,或者说是回调函数。
进程可以无视信号,可以采取默认操作,还可以自定义操作。
当handler为signal.SIG_IGN时,信号被无视(ignore)。
当handler为singal.SIG_DFL,进程采取默认操作(default)。
当handler为一个函数名时,进程采取函数中定义的操作。
'''
signal.alarm(seconds) #如果time 非0,这个函数则响应一个SIGALRM信号并在time秒后发送到该进程。
signal.alarm(0) #假如在callback函数未执行的时候,要取消的话,那么可以使用alarm(0)来取消调用该回调函数

实现

有了上面的信号基础,我们就可以用signal.alarm(t)来做超时中止,实现思路简单来说就是,在执行某函数func前,设定好一个seconds时间的闹钟信号signal.alarm(t)。这也就是意味着如果超时(也就是t秒后),会有一个信号激发信号的回调函数handler,这样只要回调函数可以引发异常就可以实现超时中止功能了;如果没有超时,则在func运行结束后使用signal.alarm(0) 取消回调函数handler的执行。

import signal,functools
class TimeoutError(Exception):pass #定义一个超时错误类
def time_out(seconds,error_msg='TIME_OUT_ERROR:No connection were found in limited time!'):
#带参数的装饰器
    def decorated(func):
        result = ''
        def signal_handler(signal_num,frame): # 信号机制的回调函数,signal_num即为信号,frame为被信号中断那一时刻的栈帧
            global result
            result = error_msg
            raise TimeoutError(error_msg) #raise显式地引发异常。一旦执行了raise语句,raise后面的语句将不能执行
        
        def wrapper(*args,**kwargs):  #def wrapper(func,*args,**kwargs):
            global result
            signal.signal(signal.SIGALRM, signal_handler)
            signal.alarm(seconds) #如果time是非0,这个函数则响应一个SIGALRM信号并在time秒后发送到该进程。
            try:
                result = func(*args,**kwargs) 
                #若超时,此时alarm会发送信息激活回调函数signal_handler,从而引发异常终止掉try的代码块
            finally:
                signal.alarm(0) #假如在callback函数未执行的时候,要取消的话,那么可以使用alarm(0)来取消调用该回调函数
                print('finish')
                return result
        return functools.wraps(func)(wrapper) #return wrapper 
    return decorated

import time

@time_out(5) #给func设定了超时时间为5s
def func():
    #可以插入http请求等代码
    time.sleep(10) #模拟超时
    return

#调用func
func()

后话

超时中止机制还可以配合threading等实现超时kill的效果
比如:论 Python 装饰器控制函数 Timeout 的正确姿势

参考文章:

Understanding Python Decorators in 12 Easy Steps!
12步轻松搞定python装饰器
深入浅出 Python 装饰器:16 步轻松搞定 Python 装饰器
深入浅出 Python 装饰器:16 步轻松搞定 Python 装饰器
Python模块之信号signal
论 Python 装饰器控制函数 Timeout 的正确姿势
python装饰器:三种函数超时机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Python 是一门极简的语言,语言简洁学习起来也是相当轻松的,但是依然有一些高级技巧,例如装饰器、协程、并发,会...
    妄心xyx阅读 5,536评论 0 23
  • 部分细节自己改了点,也加了点自己例子,基本上属于转载。转载出处:https://my.oschina.net/le...
    洛克黄瓜阅读 6,036评论 0 3
  • 本文为《爬着学Python》系列第四篇文章。从本篇开始,本专栏在顺序更新的基础上,会有不规则的更新。 在Pytho...
    SyPy阅读 7,194评论 4 11
  • 死亡瞬间发生 菜市场上的屠夫,刽子手 玻璃缸里揪出蹦哒的鱼 棒打头晕,从容淡定宰杀,刀刀鳞片淌血 忽然间幻影,刑场...
    忠志_3d7b阅读 3,550评论 1 5
  • 丢失的星星文/悠叶 秋是少女懵懂的爱情凋零是注定的结局 雨用力清洗天空的眼睛为迷失的彩虹找到 回家的轨迹 风穿过...
    悠叶_阅读 8,522评论 94 72