【问题标题】:decorator with parameter and counting how many times func called带参数的装饰器并计算 func 调用的次数
【发布时间】:2021-10-01 15:16:38
【问题描述】:

我得到了如下代码:

import functools

def change_time(_func=None, *, time=None):
    def wrapper_external(func):
        @functools.wraps(func)
        def wrapper_internal(*args, **kwargs):
            # WHAT I WANT:
            # if how_many_times_func_called >= time:
            #    how_many_times_func_called = 0
            return func(*args, **kwargs, new_time=how_many_times_func_called)
        return wrapper_internal

    if _func is None:
        return wrapper_external
    else:
        return wrapper_external(_func)


@change_time(time=3)
def my_func(some_text, new_time):
    some_text += 'abc'
    some_text += f': {new_time}'
    return some_text
    
    
print(my_func('blabla'))

我正在尝试修改,我还可以计算我使用该 func 的次数并将该值传递给包装的 func,但不知道如何修改?

【问题讨论】:

标签: python decorator


【解决方案1】:

我过去也有类似的需求,所以我做了一个可以用作装饰器的类。在这里分享代码,以防它可以帮助或启发您的解决方案:

# chrono.py
# ---------
# decorator and class to get function/method execution statistics
#
# @Chrono decorator 
# -----------------
# Wraps function/method to track time statistics
#
# @Chrono
# def myFunction(a,b,c):
#     ...
#
# for _ in range(13): myFunction(1,2,3)
#
# myfunction.stats   # prints...
#
# myFunction:
#     Last time:    0.0066 sec.
#     Total time:   0.0941 sec.
#     Call Count:   13
#     Average time: 0.0072 sec.
#     Maximum time: 0.0098 sec.
#     Recursion:    39
#     Max. Depth:   3
#
# myFunction.count     # 13
# myFunction.recursed  # 39 
# myFunction.totalTime # 0.0941
# myFunction.lastTime  # 0.0066
# myFunction.maxDepth  # 3
# myFunction.maxTime   # 0.0098
# myFunction.average   # 0.0072
#
# myFunction.clear()   # resets statistics
# myfunction.depth     # current recursion depth
#
#
# global 'chrono' object 
# ----------------------
# computes time of parameter execution
#
# t,r = chrono.time(len(str(factorial(5000)))) 
#
# returns time and result: t: 0.005331993103027344   r:16326
#
# chrono.print('5000! :',scale=1000)(len(str(factorial(5000))))
# 5.2319 5000! : 16326

from time import time
class Chrono:
     
    def __init__(self,func):
        self.func      = func
        self._name     = None
        self.clear()

    def clear(self):
        self.count     = 0
        self.recursed  = 0
        self.totalTime = 0
        self.start     = None
        self.depth     = -1
        self.lastTime  = None
        self.maxDepth  = 0
        self.maxTime   = 0

    def __call__(self,*args,**kwargs):
        self.depth    += 1
        self.count    += self.depth == 0
        self.recursed += self.depth > 0
        if self.depth == 0:
            self.start     = time()
            
        result = self.func(*args,**kwargs)
        
        if self.depth == 0:
            self.lastTime   = time()-self.start
            self.totalTime += self.lastTime
            self.maxTime    = max(self.lastTime,self.maxTime)
        else:
            self.maxDepth = max(self.maxDepth,self.depth)
        self.depth -= 1
        return result

    @property
    def name(self):
        if self._name is None:
            self._name = ""
            for n,f in globals().items():
                if f is self: self._name = n;break
        return self._name
    
    def methodCaller(self,obj):
        def withObject(*args,**kwargs):       
            return self(obj,*args,**kwargs)  # inject object instance
        return withObject

    def __get__(self,obj,objtype=None):   # return method call or CallCounter
        return self.methodCaller(obj) if obj else self

    @property
    def average(self): return self.totalTime/max(1,self.count)

    @property
    def stats(self):
        print(f"{self.name}:")
        print(f"    Last time:    {self.lastTime:3.4f} sec.")
        print(f"    Total time:   {self.totalTime:5.4f} sec.")
        print(f"    Call Count:   {self.count}")
        print(f"    Average time: {self.average:3.4f} sec.")
        print(f"    Maximum time: {self.maxTime:3.4f} sec.")
        print(f"    Recursion:    {self.recursed}")
        print(f"    Max. Depth:   {self.maxDepth}")

    @property
    def time(self):
        start = time()
        self.count += 1
        def execute(result):
            self.lastTime = time()-start
            self.totalTime += self.lastTime
            return self.lastTime,result
        return execute

    def print(self,label="",scale=1):
        start = time()
        self.count += 1
        def execute(result):
            self.lastTime = time()-start
            self.totalTime += self.lastTime
            print(f"{self.lastTime*scale:3.4f}",label,end=" ")
            if ":" in label:print(result)
            else: print()
            return result
        return execute

chrono = Chrono(None)

if __name__ == '__main__':

    @Chrono
    def myFunction(a,b,c,r=3):
        for _ in range(100000*a): pass
        if r>0: myFunction(1,b,c,r-1)
        return a+b+c

    for i in range(13): myFunction(1,2,3)

    myFunction.stats

"""
myFunction:
    Last time:    0.0062 sec.
    Total time:   0.1005 sec.
    Call Count:   13
    Average time: 0.0077 sec.
    Maximum time: 0.0106 sec.
    Recursion:    39
    Max. Depth:   3
"""

【讨论】:

  • 这对我来说完全有用 ;) 只是觉得有一种比上课更简单的方法:)
  • 该类有点矫枉过正,但由于我把它放在自己的模块中,我只需要做from chrono import Chrono 就可以将@Chrono 添加到我想要的函数/方法中分析。
【解决方案2】:

就像 sahasrara62 提到的那样

全球词典可能会解决您的问题

import functools


func_counts = {}

def change_time(_func=None, *, time=None):
    def wrapper_external(func):
        @functools.wraps(func)
        def wrapper_internal(*args, **kwargs):
            global func_counts
            key = f"{func.__module__}.{func.__name__}"
            if key in func_counts:
                func_counts[key] += 1
            else:
                func_counts[key] = 1
            
            if func_counts[key] >= time:
                func_counts[key] = 0
            how_many_times_func_called = func_counts[key]
            print(f"{key} called {how_many_times_func_called}")
            # WHAT I WANT:
            # if how_many_times_func_called >= time:
            #    how_many_times_func_called = 0
            return func(*args, **kwargs, new_time=how_many_times_func_called)
        return wrapper_internal

    if _func is None:
        return wrapper_external
    else:
        return wrapper_external(_func)

【讨论】:

    猜你喜欢
    • 2015-02-18
    • 2014-02-21
    • 2020-06-20
    • 2015-08-03
    • 2018-01-07
    • 2022-12-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多