Python: 包装库中的所有函数

6 投票
4 回答
7554 浏览
提问于 2025-04-16 20:58

我们使用的是另一个内部团队提供的一个库。(这里的比喻有点 shaky)

from externalTeam import dataCreator
datacreator.createPizza()
datacreator.createBurger()
datacreator.createHotDog()

最近我们发现他们的一个方法在某些情况下执行时间超过了一分钟。为了调试这个问题,我不得不在我们的代码中为每次调用这个方法添加超时设置。

import time
from externalTeam import dataCreator
start = time.clock()
datacreator.createPizza()
stop = time.clock()
print "It took %s seconds to perform createPizza" % (str(stop-start))

回想起来,这是因为我们在很多地方都调用了 createPizza,而我们并不控制 createPizza 这个方法(这个比喻有点不太成立了)。我更希望只在一个地方调用 createPizza,然后能在这个地方添加一个计时器。我的第一个想法是把他们的所有方法都放在我自己写的一个包装类里。但这样做就违背了 DRY 原则(不要重复自己),而且每当他们添加一个新方法时,我都得更新我们的库来包装那个新方法:

import time
from externalTeam import dataCreator
def createPizza(self):
    start = time.clock()
    datacreator.createPizza()
    stop = time.clock()
    print "It took %s seconds to perform createPizza" % (str(stop-start))

def createBurger(self):
    start = time.clock()
    datacreator.createPizza()
    stop = time.clock()
    print "It took %s seconds to perform createBurger" % (str(stop-start))

def createHotDog(self):
    start = time.clock()
    datacreator.createPizza()
    stop = time.clock()
    print "It took %s seconds to perform createHotDog" % (str(stop-start))    

我想要的是一种方法,能够在每次从 dataCreator 调用的函数周围始终执行几行代码。一定有办法通过一个中间类来实现这个,那个类的方法可以动态定义——或者说可以不定义,对吧?

4 个回答

6

如果你想要分析Python代码的性能,最好使用Python自带的性能分析库,而不是自己手动去做。

6

为什么不直接用一个简单的包装函数来调用它的参数呢?

def wrapper(func, *args, **kwargs):
    ... timing logic ...
    response = func(*args, **kwargs)
    ... more timing logic
    return response

然后这样调用它:

wrapper(datacreator.createPizza, arg1, arg2, kwarg1=kwarg)

注意,你传递的是函数本身,但并没有直接调用它。

6

我会创建一个叫做 dataCreator 的适配器类,它的工作方式如下:

  1. 有一个叫 methods2wrap 的列表,这个列表里包含了 dataCreator 中需要添加调试和计时功能的方法。
  2. 重写一个叫 __getattribute__() 的方法,这个方法会一一对应到 dataCreator 的方法,把 methods2wrap 列表中的方法包裹起来,添加一个计时的调试信息。

下面是一个概念验证的代码示例(这个例子是对 list 类进行包装,并在它的 append 方法周围插入一个调试时间戳)。

import time

class wrapper(list):

    def __getattribute__(self, name):
        TO_OVERRIDE = ['append']
        if name in TO_OVERRIDE:
            start = time.clock()
        ret = super(list, self).__getattribute__(name)
        if name in TO_OVERRIDE:
            stop = time.clock()
            print "It took %s seconds to perform %s" % (str(stop-start), name)
        return ret

profiled_list = wrapper('abc')
print profiled_list
profiled_list.append('d')
print profiled_list
profiled_list.pop()
print profiled_list

当然,你可以在这个例子的基础上进行扩展,让它变得更灵活,这样在初始化的时候就可以设置要包装哪个类,以及哪些方法需要计时……

编辑:注意,TO_OVERRIDE 在每次调用 __getattribute__ 时都会被重新赋值。这是故意设计的。如果你把它作为类属性,那么 __getattribute__ 就会陷入递归循环(你需要显式调用父类的 __getattribute__ 方法来获取它,但这样可能会比从头开始重建列表慢)。

希望这对你有帮助

撰写回答