Python功能点实现:函数级/代码块级计时器

关键词:计时器 | 装饰器 | 运行时上下文 | 代码块

工程中我们常常需要对某一个函数或者一块代码计时,从而监测系统关键位置的性能。计时方法是在代码块前后分别记录当前系统时间,然后两者相减得到代码块的耗时。最简单原始的实现类似:

from datetime import datetime

start = datetime.now()
# some code you want to measure
end = datetime.now()
print("Processing time for {} is: {} seconds".format('You Name It', elapse))

这种方式缺点明显:假如系统内有很多地方都需要计时,那么每个地方都需要插入这样的计时代码,首先是重复性工作很麻烦,其次这样会降低代码的可读性,干扰对业务逻辑的理解。本文将给出一些更好的实现,主要涉及的技术是装饰器(Decorator)运行时上下文(runtime context)

基于装饰器的函数级计时器

第一种计时器是比较常见的函数级计时器,通过装饰器完成,将原函数改装成拥有计时功能的新函数,使其可以完成运行原来函数和计时两件事。在使用时,只用在需要计时功能的函数代码前加上类似@timer的语法糖,这样每次调用原函数时,运行的将会是新函数。这样就大大减少了重复性劳动。

具体实现如下:

from datetime import datetime

def timer(func):
    '''Function Level Timer via Decorator'''
    def timed(*args, **kwargs):
        start = datetime.now()
        result = func(*args, **kwargs)
        end = datetime.now()
        elapse = (end - start).total_seconds()
        print("Processing time for {} is: {} seconds".format(func.__name__, elapse))
        return result
    return timed

@timer
def test_1(a):
    '''Function Level'''
    a *= 2
    return a

if __name__ == '__main__':
    print(test_1(1))

基于上下文的代码块级计时器

装饰器实现的计时器可以为函数添加计时功能,可以满足大部分情况的需要,但是假如我们想要更灵活一些,对任意一段连续的代码块做计时,怎样做?使用原始的插计时代码的方法显然不是我们想要的;也可以将代码块重构成一个函数,再在上面加装饰器,然而这就显得不够优雅。因此我做出了下面的实现。

首先了解上下文管理的概念。大致是说Python中允许创建一种叫上下文管理器(Context Manager)的对象,它可以管理一个代码块执行时的上下文信息。具体的方法是创建一个类,并为其实现object.__enter__object.__exit__方法,前者在进入代码块时自动执行,后者在完成代码块执行时自动执行。

在使用时,通过withas关键字,将__enter__的返回值绑定到某一个变量名,这个返回值里可以储存代码块运行过程中得到的一些信息,在这里就是运行时间啦。具体的实现是创建一个计时器类Timer,在enter时记录代码块运行的开始时间,exit时记录完成时间、计算并储存耗时到Timer实例中。在使用时,将with Timer() as t加到要计时的代码块前面,t.elapse中将会储存代码块耗时,可以任意使用。

在这个基础上,我们还可以做出一个装饰器timer_来实现基于上下文的函数级计时器。

具体实现如下:

class Timer(object):
    '''Code Block Level Timer via Context'''
    def __enter__(self):
        self.start = datetime.now()
        return self
    def __exit__(self, *args):
        self.end = datetime.now()
        self.elapse = (self.end - self.start).total_seconds()

def timer_(func):
    '''Function Level Timer via Context & with Statement'''
    def timed(*args, **kw):
        with Timer() as t:
            result = func(*args, **kw)
        print("Processing time for {} is: {} seconds".format(func.__name__, t.elapse))
        return result
    return timed

def test_2(a):
    '''Code Block Level'''
    with Timer() as t:
        a *= 2
    print("Processing time for {} is: {} seconds".format('You Name It', t.elapse))
    return a

@timer_
def test_3(a):
    '''Function Level'''
    a *= 2
    return a

if __name__ == '__main__':
    print(test_2(2))
    print(test_3(3))

更灵活的实现

更优雅地,我们还可以使用contextlib自带的ContextDecorator,参考官方示例,做出既可以with又可以作为装饰器的计时器timer_elegant

from datetime import datetime
from contextlib import ContextDecorator

class timer_elegant(ContextDecorator):
    '''Elegant Timer via ContextDecorator'''
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        self.start = datetime.now()
    def __exit__(self, *args):
        self.end = datetime.now()
        self.elapse = (self.end - self.start).total_seconds()
        print("Processing time for {} is: {} seconds".format(self.name, self.elapse))

@timer_elegant('test_4')
def test_4(a):
    a *= 2
    return a

def test_5(a):
    a *= 2
    return a

if __name__ == '__main__':
    print(test_4(4))

    with timer_elegant('test 5'):
        result_5 = test_5(5)
    print(result_5)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 223,507评论 6 521
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,626评论 3 401
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 170,466评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,456评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,473评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,016评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,409评论 3 426
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,370评论 0 278
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,909评论 1 322
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,960评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,102评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,754评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,428评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,917评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,036评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,582评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,126评论 2 362

推荐阅读更多精彩内容

  • 我只有努力再努力变得优秀变得闪闪发光 这样我才有勇气站到你面前大声说,我喜欢你好久。 曾经也觉得自己特别胖但从来没...
    小啊呀阅读 269评论 1 2
  • 今天学一个指标:速动比率 学习之前先复习下图 最后一个指标了,是不是感觉到大显身手、跃跃欲试自己的选股能力? 和“...
    风清水影阅读 430评论 0 0
  • 女人的一辈子就像无根的浮萍,飘飘荡荡,没有归属。 当她小的时候,会有父母宠她,爱她,她看起来的确属于这个家庭,这个...
    亦静初阅读 3,320评论 0 0
  • 天欲祸人,必先以微福骄之,要看他会受。天欲福人,必先以微祸儆之,要看他会救。 世人破绽处,多从周旋处见;指摘处,多...
    Sunny飞镜阅读 182评论 0 0