如何获取 Python 计时对象以在函数和装饰器范围内保留适当的信息

how to get a Python timing object to retain appropriate information within function and decorator scopes

我正在编写一个模块,以便在 Python 程序中快速轻松地计时。这个想法是可以在整个代码中创建小的时钟实例。这些时钟可用作可以启动、停止、再次启动和查询的对象。实例化的任何时钟都被添加到所有时钟的模块列表中。在程序结束时,可以请求此列表的所有时钟的打印输出(所有时钟的列表或所有类似时钟的平均值)。

我已经做了很多工作,但函数的时间安排仍然给我带来困难。具体来说,当函数 1 和 1 的测量时间应分别为 ~3 秒和 ~4 秒时,使用显式时钟或使用装饰器将函数测量的时间测量为 0。

我怀疑我没有以适当的方式保留时钟属性_startTimeTmp(为了内部计算的目的可以重置它)。

我非常感谢有关使计时器正常工作的一些指导。我对如何解决它有点困惑!

我知道代码可能看起来有点长,但我已尽我所能将其最小化,同时又不影响我正在尝试做的整体愿景(这样提出的任何建议都不会'删除关键功能)。我确实认为至少它是如何工作的相当清楚。

模块(shijian.py):

from __future__ import division
import os
import time
import uuid as uuid
import datetime
import inspect
import functools

def _main():
    global clocks
    clocks = Clocks()

def time_UTC(
    style = None
    ):
    return(
        style_datetime_object(
            datetimeObject = datetime.datetime.utcnow(),
            style = style
        )
    )

def style_datetime_object(
    datetimeObject = None,
    style = "YYYY-MM-DDTHHMMSS"
    ):
    # filename safe
    if style == "YYYY-MM-DDTHHMMSSZ":
        return(datetimeObject.strftime('%Y-%m-%dT%H%M%SZ'))
    # microseconds
    elif style == "YYYY-MM-DDTHHMMSSMMMMMMZ":
        return(datetimeObject.strftime('%Y-%m-%dT%H%M%S%fZ'))
    # elegant
    elif style == "YYYY-MM-DD HH:MM:SS UTC":
        return(datetimeObject.strftime('%Y-%m-%d %H:%M:%SZ'))
    # UNIX time in seconds with second fraction
    elif style == "UNIX time S.SSSSSS":
        return(
            (datetimeObject -\
            datetime.datetime.utcfromtimestamp(0)).total_seconds()
        )
    # UNIX time in seconds rounded
    elif style == "UNIX time S":
        return(
            int((datetimeObject -\
            datetime.datetime.utcfromtimestamp(0)).total_seconds())
        )
    # filename safe
    else:
        return(datetimeObject.strftime('%Y-%m-%dT%H%M%SZ'))

def UID():
    return(str(uuid.uuid4()))

class Clock(object):

    def __init__(
        self,
        name               = None,
        start              = True
        ):
        self._name         = name
        self._start        = start # Boolean start clock on instantiation
        self._startTime    = None # internal (value to return)
        self._startTimeTmp = None # internal (value for calculations)
        self._stopTime     = None # internal (value to return)
        self._updateTime   = None # internal
        # If no name is specified, generate a unique one.
        if self._name is None:
            self._name     = UID()
        # If a global clock list is detected, add a clock instance to it.
        if "clocks" in globals():
            clocks.add(self)
        self.reset()
        if self._start:
            self.start()

    def start(self):
        self._startTimeTmp = datetime.datetime.utcnow()
        self._startTime    = datetime.datetime.utcnow()

    def stop(self):
        self._updateTime   = None
        self._startTimeTmp = None
        self._stopTime     = datetime.datetime.utcnow()

    # Update the clock accumulator.
    def update(self):
        if self._updateTime:        
            self.accumulator += (
                datetime.datetime.utcnow() - self._updateTime
            )
        else:
            self.accumulator += (
                datetime.datetime.utcnow() - self._startTimeTmp
            )
        self._updateTime   = datetime.datetime.utcnow()

    def reset(self):
        self.accumulator   = datetime.timedelta(0)
        self._startTimeTmp = None

    # If the clock has a start time, add the difference between now and the
    # start time to the accumulator and return the accumulation. If the clock
    # does not have a start time, return the accumulation.
    def elapsed(self):
        if self._startTimeTmp:
            self.update()
        return(self.accumulator)

    def name(self):
        return(self._name)

    def time(self):
        return(self.elapsed().total_seconds())

    def startTime(self):
        if self._startTime:
            return(style_datetime_object(datetimeObject = self._startTime))
        else:
            return("none")

    def stopTime(self):
        if self._stopTime:
            return(style_datetime_object(datetimeObject = self._stopTime))
        else:
            return("none")

    def report(
        self
        ):
        string = "clock attribute".ljust(39)     + "value"
        string += "\nname".ljust(40)             + self.name()
        string += "\ntime start (s)".ljust(40)   + self.startTime()
        string += "\ntime stop (s)".ljust(40)    + self.stopTime()
        string += "\ntime elapsed (s)".ljust(40) + str(self.time())
        string += "\n"
        return(string)

    def printout(self):
        print(self.report())

def timer(function):

    #@functools.wraps(function)
    def decoration(
        *args,
        **kwargs
        ):
        arguments = inspect.getcallargs(function, *args, **kwargs)
        clock     = Clock(name = function.__name__)
        result    = function(*args, **kwargs)
        clock.stop()

    return(decoration)

class Clocks(object):

    def __init__(
        self
        ):
        self._listOfClocks       = []
        self._defaultReportStyle = "statistics"

    def add(
        self,
        clock
        ):
        self._listOfClocks.append(clock)

    def report(
        self,
        style = None
        ):
        if style is None:
            style = self._defaultReportStyle
        if self._listOfClocks != []:
            if style == "statistics":
                # Create a dictionary of clock types with corresponding lists of
                # times for all instances.
                dictionaryOfClockTypes = {}
                # Get the names of all clocks and add them to the dictionary.
                for clock in self._listOfClocks:
                    dictionaryOfClockTypes[clock.name()] = []
                # Record the values of all clocks for their respective names in
                # the dictionary.
                for clock in self._listOfClocks:
                    dictionaryOfClockTypes[clock.name()].append(clock.time())
                # Create a report, calculating the average value for each clock
                # type.
                string = "clock type".ljust(39) + "mean time (s)"
                for name, values in dictionaryOfClockTypes.iteritems():
                    string += "\n" +\
                              str(name).ljust(39) + str(sum(values)/len(values))
                string += "\n"
            elif style == "full":
                # Create a report, listing the values of all clocks.
                string = "clock".ljust(39) + "time (s)"
                for clock in self._listOfClocks:
                    string += "\n" +\
                              str(clock.name()).ljust(39) + str(clock.time())
                string += "\n"
        else:
            string = "no clocks"
        return(string)

    def printout(
        self,
        style = None
        ):
        if style is None:
            style = self._defaultReportStyle
        print(self.report(style = style))

_main()

主要代码示例(examples.py):

import shijian
import time
import inspect

def main():

    print("create clock alpha")
    alpha = shijian.Clock(name = "alpha")
    print("clock alpha start time: {time}".format(time = alpha.startTime()))
    print("sleep 2 seconds")
    time.sleep(2)
    print("clock alpha current time (s): {time}".format(time = alpha.time()))

    print("\ncreate clock beta")
    beta = shijian.Clock(name = "beta")
    print("clock beta start time: {time}".format(time = beta.startTime()))
    print("clock beta stop time: {time}".format(time = beta.stopTime()))
    print("sleep 2 seconds")
    time.sleep(2)
    print("clock beta current time (s): {time}".format(time = beta.time()))
    print("stop clock beta")
    beta.stop()
    print("clock beta start time: {time}".format(time = beta.startTime()))
    print("clock beta stop time: {time}".format(time = beta.stopTime()))
    print("sleep 2 seconds")
    time.sleep(2)
    print("clock beta start time: {time}".format(time = beta.startTime()))
    print("clock beta stop time: {time}".format(time = beta.stopTime()))
    print("clock beta current time (s): {time}".format(time = beta.time()))

    print("\nclock beta printout:\n")
    beta.printout()

    print("create two gamma clocks")
    gamma = shijian.Clock(name = "gamma")
    gamma = shijian.Clock(name = "gamma")
    print("sleep 2 seconds")
    time.sleep(2)

    print("\ncreate two unnamed clocks")
    delta = shijian.Clock()
    epsilon = shijian.Clock()
    print("sleep 2 seconds")
    time.sleep(2)

    print("\nrun function 1 (which is timed using internal clocks)")
    function1()

    print("\nrun function 2 (which is timed using a decorator)")
    function2()

    print("\nclocks full printout:\n")
    shijian.clocks.printout(style = "full")

    print("clocks statistics printout:\n")
    shijian.clocks.printout()

def function1():
    functionName = inspect.stack()[0][3]
    clock = shijian.Clock(name = functionName)
    print("initiate {functionName}".format(functionName = functionName))
    time.sleep(3)
    print("terminate {functionName}".format(functionName = functionName))
    clock.stop()

@shijian.timer
def function2():
    functionName = inspect.stack()[0][3]
    print("initiate {functionName}".format(functionName = functionName))
    time.sleep(4)
    print("terminate {functionName}".format(functionName = functionName))

if __name__ == '__main__':
    main()

示例终端输出:

create clock alpha
clock alpha start time: 2015-01-03T090124Z
sleep 2 seconds
clock alpha current time (s): 2.000887

create clock beta
clock beta start time: 2015-01-03T090126Z
clock beta stop time: none
sleep 2 seconds
clock beta current time (s): 2.002123
stop clock beta
clock beta start time: 2015-01-03T090126Z
clock beta stop time: 2015-01-03T090128Z
sleep 2 seconds
clock beta start time: 2015-01-03T090126Z
clock beta stop time: 2015-01-03T090128Z
clock beta current time (s): 2.002123

clock beta printout:

clock attribute                        value
name                                   beta
time start (s)                         2015-01-03T090126Z
time stop (s)                          2015-01-03T090128Z
time elapsed (s)                       2.002123

create two gamma clocks
sleep 2 seconds

create two unnamed clocks
sleep 2 seconds

run function 1 (which is timed using internal clocks)
initiate function1
terminate function1

run function 2 (which is timed using a decorator)
initiate function2
terminate function2

clocks full printout:

clock                                  time (s)
alpha                                  17.023659
beta                                   2.002123
gamma                                  11.018138
gamma                                  11.018138
1919f9de-85ce-48c9-b1c8-5164f3a2633e   9.017148
d24c818c-f4e6-48d0-ad72-f050a5cf86d3   9.017027
function1                              0.0
function2                              0.0

clocks statistics printout:

clock type                             mean time (s)
function1                              0.0
function2                              0.0
1919f9de-85ce-48c9-b1c8-5164f3a2633e   9.017283
beta                                   2.002123
alpha                                  17.023834
d24c818c-f4e6-48d0-ad72-f050a5cf86d3   9.017163
gamma                                  11.0182835

Clockstopped 时不会得到 updated。最小修复是:

def stop(self):
    self.update()
    self._updateTime   = None
    self._startTimeTmp = None
    self._stopTime     = datetime.datetime.utcnow()

您还有其他三个错误:

  • 您应该通过身份 (if foo is not None) 而不是真实性 (if foo) 来测试 None,以避免 False-y 值不是 None;
  • shijian.timer 不会 return result,所以尽管计时会起作用,但您会破坏任何期望来自修饰函数的 return 的代码;和
  • 如果你想让代码在Python 2和3中工作,你不能使用dict.iteritems,后者不存在。如果您只希望它在 Python 2 中工作,请 from __future__ import print_function 或使用 print whatever 而不是 print(whatever).

此外,您的代码根本不符合 the style guide(或者,更糟的是,甚至内部一致 - 例如,将 Clock.start 的定义与 Clock.report 的定义进行比较) .

在设计和功能上也有改进的空间(例如 Clock.name 可以是 @property,我会将 table 打印与结果生成分开。一旦满足以下条件,您应该考虑为 Code Review 提交代码:

  • 完成;
  • 已测试;和
  • 符合风格指南

(您可能会发现使用 pylint 对后者有帮助)。

最后,我假设您这样做是出于学习目的,而不是因为您需要功能,如 Python has its own profilers.