Python: обернуть все функции в библиотеке - PullRequest
5 голосов
/ 07 июля 2011

Мы используем библиотеку, предоставленную другой внутренней командой. (Шаткая аналогия начинается сейчас)

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 голосов
/ 07 июля 2011

Если вы пытаетесь профилировать код Python, вы должны использовать встроенные в Python библиотеки профилирования вместо того, чтобы пытаться сделать это вручную.

4 голосов
/ 07 июля 2011

Я бы создал dataCreator класс адаптера, который работал бы так:

  1. Иметь methods2wrap список методов из dataCreator, которые необходимо включить в функцию отладки / синхронизации.
  2. Имейте переопределенное значение __getattribute__(), которое отображало бы 1: 1 на методы 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

Конечно, вы могли бы построить этот пример и сделать его параметрическим, чтобы во время инициализации вы могли установить, какой класс переносить и какие методы следует синхронизировать ...

EDIT: обратите внимание, что TO_OVERRIDE переназначается при каждом __getattribute__ вызове. Это по замыслу. Если вы сделаете это как атрибут класса, __getattribute__ будет рекурсивно зацикливаться (вы должны использовать явный вызов родительского метода __getattribute__, чтобы получить его, но это, вероятно, будет медленнее, чем просто перестроить список с нуля.

НТН

4 голосов
/ 07 июля 2011

Почему бы не одна функция-обертка, которая просто вызывает свой аргумент?

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

и назовите его:

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

обратите внимание, что вы передаете саму функцию, но не вызываете ее.

0 голосов
/ 17 сентября 2018

Может помочь следующий шаблон:

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