Предложения для класса профайлер в Python


Вот что у меня сейчас:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import functools
import math
import time

class Timer(object):
    def __init__(self):
        self.__values = []

    def start(self):
        self.__start = time.time()

        return self

    def stop(self):
        self.__values.append(int(round((time.time() - self.__start) * 1000)))
        self.__start = None

        return self

    @property
    def average(self):
        return sum(self.__values) / len(self.__values) 

    @property
    def deviation(self):
        if self.average:
            return math.sqrt(sum((v - self.average) ** 2 for v in self.__values) / self.runs)

        return 0

    @property
    def elapsed(self):
        return sum(self.__values)

    @property
    def runs(self):
        return len(self.__values)

class Profiler(object):
    __timers = {}

    @staticmethod
    def info(timer_id):
        if not Profiler.__timers.has_key(timer_id):
            raise Exception('Timer not started')

        return Profiler.__timers[timer_id]

    @staticmethod
    def profile(f):
        @functools.wraps(f)
        def wrap(self=None, *args, **kwargs):
            method = self.__class__.__name__ + '.' + f.func_name if self \
                     else f.func_name

            Profiler.start(method)
            r = f(self, *args, **kwargs) if self else f(*args, **kwargs)
            Profiler.stop(method)

            return r

        return wrap

    @staticmethod
    def reset(timer_id):
        Profiler.__timers[timer_id] = Timer()

    @staticmethod
    def start(timer_id):
        if not Profiler.__timers.has_key(timer_id):
            Profiler.reset(timer_id)

        Profiler.__timers[timer_id].start()

    @staticmethod
    def stop(timer_id):
        if not Profiler.__timers.has_key(timer_id):
            raise Exception('Timer not started')

        Profiler.__timers[timer_id].stop()

if __name__ == '__main__':
    class Test(object):
        def isPrime(self, n):
            if n < 2 or (n % 2) == 0:
                return n == 2

            f = 3

            while (f * f) <= n:
                if (n % f) == 0:
                    return False

                f += 2

            return True

        @Profiler.profile
        def run(self):
            return filter(self.isPrime, range(1, 1000001))

    test = Test()

    for x in range(5):
        test.run()

    p = Profiler.info('Test.run')
    print 'runs = %d' % p.runs
    print 'elapsed = %d ms' % p.elapsed
    print 'average = %d ms' % p.average
    print 'deviation = %d ms' % p.deviation

Я ищу предложения, чтобы улучшить это, и вы можете предложить. :)

Профилировщик.профиль декоратор поддерживает обе функции и методы класса. Я написал небольшой тест, чтобы продемонстрировать мой профайлер.



396
5
задан 28 февраля 2011 в 03:02 Источник Поделиться
Комментарии
1 ответ

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import functools
import math
import time

class Timer(object):
def __init__(self):
self.__values = []

Вы используете префикс __ для обозначения частная attributess. Это вполне допустимо и имеет специальную поддержку в Python. Сказав это, я не фанат его. Он предназначен для использования, когда вы находитесь подклассы объекты в целях предотвращения несчастных случаев. Я думаю, что если вы не проектировал ваш объект, чтобы быть разделен на подклассы, вы должны придерживаться одного подчеркивания.

    def start(self):
self.__start = time.time()

return self

Некоторые системы делают использование этого возвращения парадигмы самости. Правда, его немного необычным и приведет к Код быть запутанным, если вы не привыкли к этому. Как вам кажется, не делает использование этого. Я бы просто избавиться от вернуть себя. Это, кажется, не быть тип объекта, который хорошо работает с шаблоном в любом случае.

    def stop(self):
self.__values.append(int(round((time.time() - self.__start) * 1000)))

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

seconds_elapsed = time.time() - self.__start
milliseconds_elapsed = int(round(seconds_elapsed*1000))
self.__values.append(milliseconds_elapsed)

А еще одно замечание, Не бойтесь поплавков. Вы, наверное, лучше хранить список с плавающей запятой секунд, а затем целое число миллисекунд. Код будет понятнее и, наверное, нет заметной разницы в эффективности.

        self.__start = None

return self

@property
def average(self):
return sum(self.__values) / len(self.__values)

@property
def deviation(self):
if self.average:
return math.sqrt(sum((v - self.average) ** 2 for v in self.__values) / self.runs)

return 0

Я рекомендую использовать библиотеки библиотеки numpy, который имеет функции для расчета среднее/стандартное отклонение/почти все. Математика выполнена в пакете numpy будет гораздо более эффективно, чем ваши версии Python.

    @property
def elapsed(self):
return sum(self.__values)

@property
def runs(self):
return len(self.__values)

Мне не нравится таймер имя класса, потому что он тратит большую часть своих усилий отслеживать значения не измеряя времени.

class Profiler(object):
__timers = {}

@staticmethod
def info(timer_id):
if not Profiler.__timers.has_key(timer_id):
raise Exception('Timer not started')

return Profiler.__timers[timer_id]

Если вы собираетесь хранить состояние объекта класса, использовать classmethod в как так:

    @classmethod
def info(cls, timer_id):
if not cls.__timers.has_key(timer_id):
raise Exception('Timer not started')

return cls.__timers[timer_id]

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

    def info(self, timer_id):
if not self.__timers.has_key(timer_id):
raise Exception('Timer not started')

return self.__timers[timer_id]

Кроме того, не бойтесь исключений. В Python, вы не должны (в общем) пытаются определить, является ли исключение будет брошен перед началом операции. Просто выполнить операцию и поймать исключение. Выше версия заканчивается локализации элемента в словарь два раза, что это просто пустая трата. Выше функция должна быть реализована как:

    def info(self, timer_id):
try:
return self.__timers[timer_id]
except KeyError:
raise Exception('Timer not started')

Аналогичные замечания применимы к другим функциям.

    @staticmethod
def profile(f):
@functools.wraps(f)
def wrap(self=None, *args, **kwargs):

Вы предполагаете, что ваш первый параметр будет (если он существует). В зависимости от того, как это называется, Я может уже быть в комплекте в F как объект способ. Кроме того, если вы вызываете функцию, которая не является способом что-то еще придет туда. Вы не должны запечатлеть себя таким образом, просто дайте все параметры идут на *аргументы

            method = self.__class__.__name__ + '.' + f.func_name if self \
else f.func_name

Как уже упоминалось, вы не должны захватить себя, как вы делаете. Правильное место, чтобы найти класс Ф.im_class. Будьте осторожны! Этот атрибут не будет там на методы.

            Profiler.start(method)
r = f(self, *args, **kwargs) if self else f(*args, **kwargs)

Я повторно вызываю Р, return_value для большей ясности. Кроме того, устраняя себя, вы будете делать этот кусок кода чище
Профайлером.остановка(метод)

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

            return r

return wrap

Ваш API является нелогичным на данный момент. Я бы кроме функция называется профиль профиль функция, не возвращает новую функцию, которая тогда мне нужно позвонить для того, чтобы профиль его. Для какой функции оно должно называться профилированным.

    @staticmethod
def reset(timer_id):
Profiler.__timers[timer_id] = Timer()

@staticmethod
def start(timer_id):
if not Profiler.__timers.has_key(timer_id):
Profiler.reset(timer_id)

Profiler.__timers[timer_id].start()

@staticmethod
def stop(timer_id):
if not Profiler.__timers.has_key(timer_id):
raise Exception('Timer not started')

Profiler.__timers[timer_id].stop()

if __name__ == '__main__':
class Test(object):
def isPrime(self, n):
if n < 2 or (n % 2) == 0:
return n == 2

f = 3

while (f * f) <= n:
if (n % f) == 0:
return False

f += 2

return True

@Profiler.profile
def run(self):
return filter(self.isPrime, range(1, 1000001))

test = Test()

for x in range(5):
test.run()

p = Profiler.info('Test.run')
print 'runs = %d' % p.runs
print 'elapsed = %d ms' % p.elapsed
print 'average = %d ms' % p.average
print 'deviation = %d ms' % p.deviation

Я думаю, что лучшее API для реализации будет та, которая работает так:

    class Test(object):
def isPrime(self, n):
if n < 2 or (n % 2) == 0:
return n == 2

f = 3

while (f * f) <= n:
if (n % f) == 0:
return False

f += 2

return True

@profiled
def run(self):
return filter(self.isPrime, range(1, 1000001))

test = Test()

for x in range(5):
test.run()

stats = Test.run.stats
print 'runs = %d' % stats.runs
print 'elapsed = %d ms' % stats.elapsed
print 'average = %d ms' % stats.average
print 'deviation = %d ms' % stats.deviation

По сути, сохраняет объект таймера в функцию сам объект. Это позволит значительно упростить код, потому что вы используете разрешение имен в Python, а затем самостоятельно.

Как окончательное Примечание, есть много хороших средств профилирования там для Python. Вы, вероятно, лучше с помощью одной из них.

6
ответ дан 2 марта 2011 в 10:03 Источник Поделиться