1

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?

  • 1
    Does this answer your question? [is there a way to track the number of times a function is called?](https://stackoverflow.com/questions/21716940/is-there-a-way-to-track-the-number-of-times-a-function-is-called) – sahasrara62 Oct 01 '21 at 15:47
  • 1
    if you still want to solve this then use a global dicitonary variable to save the no of time a function is called and wheneven a function is called just increase the count – sahasrara62 Oct 01 '21 at 15:58

2 Answers2

1

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
"""
Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • That's totally works for me ;) just thought there is some kind of easier way to do it than a class :) – Niewasz Biznes Oct 04 '21 at 06:59
  • That class is a bit of an overkill but, since I put it in its own module, I only have to do `from chrono import Chrono` to be able to add `@Chrono` to the functions/methods I want to analyze. – Alain T. Oct 04 '21 at 07:02
0

just as sahasrara62 mentions

a global dictionary may save your issue

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)
Alan
  • 61
  • 1
  • 4