记录有关请求的统计信息时替代全局变量

Alternative to global variables when logging stats about requests

我有一个程序可以记录一些关于我下载的数据的消息。除此之外,我想显示一些关于我对站点发出的每个 k 请求的请求的统计信息(在我的例子中 k 是 10)+ 执行结束时的一些总体统计信息。

目前我有一个我不满意的实现,因为它使用了全局变量。我正在寻找更清洁的替代品。它看起来像这样(注意:请忽略我使用 print 而不是 logging 并且我正在使用 time.time 而不是 time.perf_counter 来测量时间的流逝这一事实(在这里阅读后者将是更好的选择):

import time
import pprint

def f2(*args, **kwargs):
    global START_TIME

    global NO_REQUESTS
    global TOTAL_TIME_FOR_REQUESTS
    global MAX_TIME_FOR_REQUEST
    global AVERAGE_TIME_FOR_REQUESTS

    global TOTAL_TIME_FOR_DECODING
    global TOTAL_TIME_FOR_INTERSECT
    
    # ... logic that changes values of most of these global variables

    if NO_REQUESTS % 10 == 0:
        AVERAGE_TIME_FOR_REQUESTS = TOTAL_TIME_FOR_REQUESTS / NO_REQUESTS
        print()
        print('no requests so far: ' + str(NO_REQUESTS))
        print('average request time: {:.2f}s'.format(AVERAGE_TIME_FOR_REQUESTS))
        print('max request time: {:.2f}s'.format(MAX_TIME_FOR_REQUEST))
                    
        elapsed = time.time() - START_TIME
        hours_elapsed = elapsed // 3600
        minutes_elapsed = (elapsed % 3600) // 60
        seconds_elapsed = ((elapsed % 3600) % 60)
        print('time elapsed so far: {}h {}m {:.2f}s'.format(hours_elapsed, minutes_elapsed, seconds_elapsed))
        print()

        time5 = time.time()
        decoded = some_module.decode(res.content)
        time6 = time.time()

        elapsed2 = time6 - time5
        TOTAL_TIME_FOR_DECODING += elapsed2

    return something


def f1(*args, **kwargs):

    global START_TIME

    global TOTAL_TIME_FOR_REQUESTS
    TOTAL_TIME_FOR_REQUESTS = 0
    global MAX_TIME_FOR_REQUEST
    MAX_TIME_FOR_REQUEST = 0
    global NO_REQUESTS
    NO_REQUESTS = 0
    global AVERAGE_TIME_FOR_REQUESTS
    AVERAGE_TIME_FOR_REQUESTS = 0

    global TOTAL_TIME_FOR_DECODING
    TOTAL_TIME_FOR_DECODING = 0
    global TOTAL_TIME_FOR_INTERSECT
    TOTAL_TIME_FOR_INTERSECT = 0

    f2() # notice call to other function!

    # ... some logic
        
    return some_results


def output_final_stats(elapsed, results, precision='{:.3f}'):
    print()
    print('=============================')

    hours_elapsed = elapsed // 3600
    minutes_elapsed = (elapsed % 3600) // 60
    seconds_elapsed = ((elapsed % 3600) % 60)
    print("TIME ELAPSED: {:.3f}s OR {}h {}m {:.3f}s".format(
        elapsed, hours_elapsed, minutes_elapsed, seconds_elapsed))

    print("out of which:")
    # print((precision+'s for requests)'.format(TOTAL_TIME_FOR_REQUESTS)))
    print('{:.3f}s for requests'.format(TOTAL_TIME_FOR_REQUESTS))
    print('{:.3f}s for decoding'.format(TOTAL_TIME_FOR_DECODING))
    print('{:.3f}s for intersect'.format(TOTAL_TIME_FOR_INTERSECT))

    total = TOTAL_TIME_FOR_REQUESTS + TOTAL_TIME_FOR_DECODING + TOTAL_TIME_FOR_INTERSECT
    print('EXPECTED: {:.3f}s'.format(total))
    print('DIFF: {:.3f}s'.format(elapsed - total))
    print()
    print('AVERAGE REQUEST TIME: {:.3f}s'.format(AVERAGE_TIME_FOR_REQUESTS))
    print('TOTAL NO. REQUESTS: ' + str(NO_REQUESTS))
    print('MAX REQUEST TIME: {:.3f}s'.format(MAX_TIME_FOR_REQUEST))
    print('TOTAL NO. RESULTS: ' + str(len(results)))
    pprint('RESULTS: {}'.format(results), indent=4)


if __name__ == '__main__':

    START_TIME = time.time()
    results = f1(some_params)
    final_time = time.time()

    elapsed = final_time - START_TIME
    output_final_stats(elapsed, results)


我的想法(不确定是否是最好的选择,对替代方案开放)是以某种方式在 NO_REQUESTS 变量上有一个监听器,每当该数字达到 10 的倍数时触发记录我感兴趣的变量。尽管如此,我应该在哪里存储这些变量,它们的命名空间是什么?

另一种选择可能是为我的一个函数使用参数化装饰器,但在这种情况下,我不确定将我感兴趣的值从一个函数传递到另一个函数有多容易。

我认为最干净的方法是使用参数化 class 装饰器。

class LogEveryN:
    def __init__(self, n=10):
        self.n = n
        self.number_of_requests = 0
        self.total_time_for_requests = 0
        self.max_time_for_request = 0
        self.average_time_for_request = 0

    def __call__(self, func, *args, **kwargs):
        def wrapper(*args, **kwargs):
            self.number_of_request += 1

            if self.number_of_request % self.n:
                # Do your computation and logging

            return func(*args, **kwargs)
        return wrapper

@LogEveryN(n=5)
def request_function():
    pass