Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/logging/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
用于跟踪/记录运行时间的优雅Python解决方案?_Python_Logging_Python Decorators - Fatal编程技术网

用于跟踪/记录运行时间的优雅Python解决方案?

用于跟踪/记录运行时间的优雅Python解决方案?,python,logging,python-decorators,Python,Logging,Python Decorators,为了在日志中捕获有意义的运行时间信息,我在许多函数中复制了以下时间捕获和日志代码: import time import datetime def elapsed_str(seconds): """ Returns elapsed number of seconds in format '(elapsed HH:MM:SS)' """ return "({} elapsed)".format(str(datetime.timedelta(seconds=int(seconds)

为了在日志中捕获有意义的运行时间信息,我在许多函数中复制了以下时间捕获和日志代码:

import time
import datetime

def elapsed_str(seconds):
    """ Returns elapsed number of seconds in format '(elapsed HH:MM:SS)' """
    return "({} elapsed)".format(str(datetime.timedelta(seconds=int(seconds))))

def big_job(job_obj):
    """ Do a big job and return the result """
    start = time.time()
    logging.info(f"Starting big '{job_obj.name}' job...")
    logging.info(f"Doing stuff related to '{job_type}'...")
    time.sleep(10)  # Do some stuff...
    logging.info(f"Big '{job_obj.name}' job completed! "
                 f"{elapsed_str(time.time() - start)}")
    return my_result
使用示例输出:

big_job("sheep-counting")
# Log Output:
#   2019-09-04 01:10:48,027 - INFO - Starting big 'sheep-counting' job...
#   2019-09-04 01:10:48,092 - INFO - Doing stuff related to 'sheep-counting'
#   2019-09-04 01:10:58,802 - INFO - Big 'sheep-counting' job completed! (0:00:10 elapsed)
我正在寻找一种优雅的pythonic方法来消除每次重写这些冗余行:

start=time.time-应该在函数启动时自动捕获开始时间。 time.time-开始应使用以前捕获的开始时间,并从现在开始推断当前时间。理想情况下,RU str可以用零参数调用。
我的具体用例是在数据科学/数据工程领域生成大型数据集。运行时可以是几秒到几天,重要的是,1在本例中,可以轻松搜索日志中的“已用”一词;2添加日志的开发人员成本非常低,因为我们无法提前知道哪些作业可能会很慢,而且一旦发现性能问题,我们可能无法修改源代码。

如果我理解正确,您可以编写一个将对函数计时的装饰器


这里有一个很好的例子:

推荐的方法是从3.7开始使用time.perf_计数器和time.perf_计数器

为了度量函数的运行时,使用装饰器是比较合适的。例如,下面的一个:

import time

def benchmark(fn):
    def _timing(*a, **kw):
        st = time.perf_counter()
        r = fn(*a, **kw)
        print(f"{fn.__name__} execution: {time.perf_counter() - st} seconds")
        return r

    return _timing

@benchmark
def your_test():
    print("IN")
    time.sleep(1)
    print("OUT")

your_test()

c这个装饰器的代码从

稍微修改了一下,这对于其他人的用例来说可能有些过分,但是我发现的解决方案需要一些困难,我将在这里为任何想要完成类似任务的人记录它们

1.用于动态计算f字符串的Helper函数 2.@logged decorator类 示例用法:

@logged()
def my_func_a():
    pass
# 2019-08-18 - INFO - Beginning call to my_func_a()...
# 2019-08-18 - INFO - Completed call to my_func_a()  (00:00:00 elapsed)


@logged(log_fn=logging.debug)
def my_func_b():
    pass
# 2019-08-18 - DEBUG - Beginning call to my_func_b()...
# 2019-08-18 - DEBUG - Completed call to my_func_b()  (00:00:00 elapsed)


@logged("doing a thing")
def my_func_c():
    pass
# 2019-08-18 - INFO - Beginning doing a thing...
# 2019-08-18 - INFO - Completed doing a thing  (00:00:00 elapsed)


@logged("doing a thing with {foo_obj.name}")
def my_func_d(foo_obj):
    pass
# 2019-08-18 - INFO - Beginning doing a thing with Foo...
# 2019-08-18 - INFO - Completed doing a thing with Foo  (00:00:00 elapsed)


@logged("doing a thing with '{custom_kwarg}'", custom_kwarg="foo")
def my_func_e(foo_obj):
    pass
# 2019-08-18 - INFO - Beginning doing a thing with 'foo'...
# 2019-08-18 - INFO - Completed doing a thing with 'foo'  (00:00:00 elapsed)
结论 与更简单的装饰解决方案相比,其主要优势是:

通过利用f字符串的延迟执行,并通过从decorator构造函数以及函数调用本身注入上下文变量,可以轻松地定制日志消息,使其具有可读性。 最重要的是,几乎任何函数参数的派生都可以用来区分后续调用中的日志,而不改变函数本身的定义方式。 高级回调场景可以通过将函数或复杂对象发送到decorator参数desc_detail来实现,在这种情况下,函数将在函数执行之前和之后进行求值。这最终可以扩展为使用回调函数对创建的数据表中的行进行计数,例如,在完成日志中包括表行计数。
非常感谢。这是非常接近我需要的!链接问题中似乎缺少的是自定义消息的功能。在本例中,这将是打印job_类型参数,但现实世界中的参数可能更复杂,只需打印全部或第一个参数。更现实的例子可能是包含name属性的字典或对象。更新了问题以调用job_obj.name而不是job_type,这在用例中更现实一些。通用方法打印函数名,这很有帮助,但它们似乎不允许日志消息本身具有灵活性。这很有帮助,但不幸的是,它不能像我的用例那样工作,因为这种方法不允许我自定义日志消息。具有不同参数的多个调用将创建彼此无法区分的日志。您可以在decorator中实现如何记录日志的逻辑。它可以访问函数对象以及使用args、kwargs调用它的所有上下文。您还可以在类内修饰函数,请参见我的链接中的原始示例。无论如何,这绝对是你要求的“蟒蛇式”方式。
class logged(object):
    """
    Decorator class for logging function start, completion, and elapsed time.
    """

    def __init__(
        self,
        desc_text="'{desc_detail}' call to {fn.__name__}()",
        desc_detail="",
        start_msg="Beginning {desc_text}...",
        success_msg="Completed {desc_text}  {elapsed}",
        log_fn=logging.info,
        **addl_kwargs,
    ):
        """ All arguments optional """
        self.context = addl_kwargs.copy()  # start with addl. args
        self.context.update(locals())  # merge all constructor args
        self.context["elapsed"] = None
        self.context["start"] = time.time()

    def re_eval(self, context_key: str):
        """ Evaluate the f-string in self.context[context_key], store back the result """
        self.context[context_key] = fstr(self.context[context_key], locals=self.context)

    def elapsed_str(self):
        """ Return a formatted string, e.g. '(HH:MM:SS elapsed)' """
        seconds = time.time() - self.context["start"]
        return "({} elapsed)".format(str(datetime.timedelta(seconds=int(seconds))))

    def __call__(self, fn):
        """ Call the decorated function """

        def wrapped_fn(*args, **kwargs):
            """
            The decorated function definition. Note that the log needs access to 
            all passed arguments to the decorator, as well as all of the function's
            native args in a dictionary, even if args are not provided by keyword.
            If start_msg is None or success_msg is None, those log entries are skipped.
            """
            self.context["fn"] = fn
            fn_arg_names = inspect.getfullargspec(fn).args
            for x, arg_value in enumerate(args, 0):
                self.context[fn_arg_names[x]] = arg_value
            self.context.update(kwargs)
            desc_detail_fn = None
            log_fn = self.context["log_fn"]
            # If desc_detail is callable, evaluate dynamically (both before and after)
            if callable(self.context["desc_detail"]):
                desc_detail_fn = self.context["desc_detail"]
                self.context["desc_detail"] = desc_detail_fn()

            # Re-evaluate any decorator args which are fstrings
            self.re_eval("desc_detail")
            self.re_eval("desc_text")
            # Remove 'desc_detail' if blank or unused
            self.context["desc_text"] = self.context["desc_text"].replace("'' ", "")
            self.re_eval("start_msg")
            if self.context["start_msg"]:
                # log the start of execution
                log_fn(self.context["start_msg"])
            ret_val = fn(*args, **kwargs)
            if desc_detail_fn:
                # If desc_detail callable, then reevaluate
                self.context["desc_detail"] = desc_detail_fn()
            self.context["elapsed"] = self.elapsed_str()
            # log the end of execution
            log_fn(fstr(self.context["success_msg"], locals=self.context))
            return ret_val

        return wrapped_fn
@logged()
def my_func_a():
    pass
# 2019-08-18 - INFO - Beginning call to my_func_a()...
# 2019-08-18 - INFO - Completed call to my_func_a()  (00:00:00 elapsed)


@logged(log_fn=logging.debug)
def my_func_b():
    pass
# 2019-08-18 - DEBUG - Beginning call to my_func_b()...
# 2019-08-18 - DEBUG - Completed call to my_func_b()  (00:00:00 elapsed)


@logged("doing a thing")
def my_func_c():
    pass
# 2019-08-18 - INFO - Beginning doing a thing...
# 2019-08-18 - INFO - Completed doing a thing  (00:00:00 elapsed)


@logged("doing a thing with {foo_obj.name}")
def my_func_d(foo_obj):
    pass
# 2019-08-18 - INFO - Beginning doing a thing with Foo...
# 2019-08-18 - INFO - Completed doing a thing with Foo  (00:00:00 elapsed)


@logged("doing a thing with '{custom_kwarg}'", custom_kwarg="foo")
def my_func_e(foo_obj):
    pass
# 2019-08-18 - INFO - Beginning doing a thing with 'foo'...
# 2019-08-18 - INFO - Completed doing a thing with 'foo'  (00:00:00 elapsed)