Search code examples
pythondecorator

decorator with parameter and counting how many times func called


I got a code like below:

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'))

and i am trying to modify that i can also count how many times i was using that func and pass that value for wrapped func, but got no idea how to modify that?


Solution

  • I had a similar need in the past so I made a class that can be used as a decorator. Sharing the code here in case it could help or inspire your solution:

    # 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
    """