0

I need to keep track of the number of times each function in a collection has been called. If a function is called more than x times within n seconds, my program needs to pause, after which the count for that function is reset.

My functions calls might look something like this:

a(1)

b(1,param2=2, param3=3)

c(1,param3=3)

My best idea is to have a wrapper function keep track of all of the limits. Something like

def wrapper(function, function_params,x,n):
    if not hasattr(wrapper, "function_dict"):
        wrapper.function_dict = {}
    if function not in wrapper.function_dict.keys():
        wrapper.function_dict[function] = {
            remaining = x, expires = time.time() + n
            }

    remaining = wrapper.function_dict[function]['remaining']
    expires = wrapper.function_dict[function]['expires']

    if remaining == 0:
        time.sleep(expires - time.time())
        wrapper.function_dict[function] = {
            remaining = x, expires = time.time() + n
            }

    results = ????? # call function, this is what I don't know how to do
        wrapper.function_dict[function]['remaining'] -= 1

My question is, how to I handle the parameters for the functions? I'm not sure how exactly to account for the fact that there might be a variable number of parameters, and that some might be named. For example, the function definition for c might be:

def c(param1,param2=2, param3=3):
    return param1 + param2 + param3

But I might need to call it with only param1 and param3.

Do I have the right general approach? This feels like something I could accomplish with the ** operator, but I'm stuck on how exactly to proceed.

wils484
  • 275
  • 1
  • 3
  • 14

2 Answers2

4

Write a decorator, and use a splat operator to handle arbitrary arguments.

Example:

def pause_wrapper(x, n):
    def decorator(f):
        config = [x, time.time()+n]
        def wrapped(*args, **kwargs):
            if config[0] == 0:
                time.sleep(config[1] - time.time())
                config = [x, time.time() + n]

            return f(*args, **kwargs)
        return wrapped
    return decorator

and usage:

@pause_wrapper(x, n)
def function(a, b, c):
    ...

The *args and **kwargs are informally called "splat" arguments. A function that takes *args, **kwargs receives all positional parameters in the tuple args and all keyword arguments in the dictionary kwargs. (You can have other arguments besides the splats, in which case the splats soak up all arguments not sent to named arguments).

Passing *args and **kwargs has the opposite effect, passing the contents of args as extra positional parameters, and kwargs as keyword parameters.

Using both allows you to handle any set of arguments, in or out, letting you do transparent wrapping (like this example).

nneonneo
  • 171,345
  • 36
  • 312
  • 383
0

this is basically what decorators were made for

from collections import defaultdict
class counted:
     calls = defaultdict(int)
     def __init__(self,x,n):
         self.x = x
         self.n = n
     def __call__(self,fn,*args,**kwargs):
         results = fn(*args,**kwargs)
         calls[fn.__name__] += 1
         #do something with the count ...


 @counted(3,9)
 def functionWhatever(arg1,arg2,arg3,**kwargs):
      return "55"

 functionWhatever(1,2,3,something=5)
Joran Beasley
  • 110,522
  • 12
  • 160
  • 179