简体   繁体   English

Python:将所有函数包装在一个库中

[英]Python: wrap all functions in a library

We use a library provided by another internal team.我们使用另一个内部团队提供的库。 (Shaky analogy starts now) (摇摇欲坠的类比现在开始)

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

Recently we found a single method of theirs was taking over a minute to execute in certain situations.最近我们发现他们的一种方法在某些情况下需要花费一分钟以上的时间来执行。 To debug this, I had to go into our code and add timeouts around every call of this method.为了调试它,我必须在我们的代码中添加 go 并在每次调用此方法时添加超时。

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))

In hindsight, that's because we're calling createPizza all over the place, and we don't control createPizza itself (the analogy is starting to break down a little here).事后看来,那是因为我们到处都在调用 createPizza,而且我们不控制 createPizza 本身(这个类比在这里开始有点不成立了)。 I'd rather just call createPizza in one place, and be able to add a timer around that.我宁愿只在一个地方调用 createPizza,并能够围绕它添加一个计时器。 My first thought to accomplish this would be to create a wrap all their methods in my own wrapper class.我首先想到的是在我自己的包装器 class 中创建一个包装他们所有的方法。 That's the opposite of DRY though, and anytime they add another method I'd have to update our library to wrap that as well:这与 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))    

What I want is a way to always execute a few lines of code around every function that's being called from dataCreator.我想要的是一种始终围绕从 dataCreator 调用的每个 function 执行几行代码的方法。 There must be some way to do that through an intermediate class whose methods can be dynamically defined - or rather left undefined, right?必须有某种方法可以通过中间 class 来做到这一点,其方法可以动态定义 - 或者更确切地说是未定义,对吧?

If you're trying to profile Python code, you should use Python's built-in profiling libraries instead of trying to do it manually.如果您尝试分析 Python 代码,您应该使用 Python 的内置分析库而不是尝试手动进行。

Why not a single wrapper function which just calls its argument?为什么不是一个只调用其参数的包装器 function 呢?

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

and call it:并称之为:

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

note you pass the function itself, but without calling it.请注意,您传递了 function 本身,但没有调用它。

I would create a dataCreator adapter class that would work like this:我将创建一个dataCreator适配器 class ,它将像这样工作:

  1. Have a methods2wrap list of the methods from dataCreator that needs to be wrapped into the debugging/timing functionality.有一个来自dataCreator的方法的methods2wrap列表,这些方法需要包装到调试/计时功能中。
  2. Have an overridden __getattribute__() that would map 1:1 onto the dataCreator methods, wrapping the methods in methods2wrap into a timing debug message.有一个覆盖的__getattribute__() ,它将 map 1:1 到dataCreator方法上,将methods2wrap中的方法包装到时序调试消息中。

Proof-of-concept code (the example wrap the class list and insert a debugging timestamp around its method append ).概念验证代码(示例包装 class 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

Of course you could build on this example and make it parametric, so that at initialisation time you can set what class to wrap and what methods should be timed...当然,您可以在此示例的基础上构建并使其参数化,以便在初始化时您可以设置要包装的 class 以及应该计时的方法......

EDIT: Note that TO_OVERRIDE is reassigned at each __getattribute__ call.编辑:请注意,在每次__getattribute__调用时都会重新分配TO_OVERRIDE This is by design.这是设计使然。 If you you would make it as a class attribute, __getattribute__ would recursively loop (you should use an explicit call to the parent __getattribute__ method to retrieve it, but this would probably be slower than simply rebuild the list from scratch.如果您将其设置为 class 属性,则__getattribute__将递归循环(您应该使用对父__getattribute__方法的显式调用来检索它,但这可能比简单地从头开始重建列表要慢。

HTH高温高压

The following template could help:以下模板可能会有所帮助:

class MeteredClient(Client):
  def __init__(self, *args, **kwargs):
    super(MeteredClient, self).__init__(*args, **kwargs)

  def __getattribute__(self, method_name):
    attribute = super(Client, self).__getattribute__(method_name)

    if not inspect.ismethod(attribute):
      return attribute

    metric = TIMINGS.labels(method_name)

    def decorator(*args, **kw):
      start_time = get_time()
      rv = attribute(*args, **kw)
      metric.observe(get_time() - start_time)
      return rv

    return decorator

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM