SoFunction
Updated on 2024-11-17

python decorator - a way to limit the number of function calls (one call in 10s)

This is the blogger's recent interview question for a large company, write a decorator that limits functions to be called every 10s. At that time it was a written test, only wrote the approximate code, came back to revise the basics of python decorator, finished the code. I decided to write a blog to record it.

Decorators are categorized into decorators with parameters and decorators without parameters.

# Decorators without parameters
@dec1
@dec2
def func():
  ...
# This function declaration is equivalent to
func = dec1(dec2(func))
# Decorators with parameters
@dec(some_args)
def func():
  ...
# This function declaration is equivalent to
func = dec(some_args)(func)

Some details to note about decorators without parameters

1. On the decorator function (decorator) itself

Therefore, a decorator generally corresponds to two functions, one is the decorator function, which is used to handle some initialization operations, and the other is decorated_func, which is used to implement additional processing of the decorated function func. And in order to keep the reference to func, decorated_func is usually used as an internal function of decorator.

def decorator(func):
  def decorator_func()
    func()
  return decorated_func

The decorator function is called only once at the time of function declaration

Decorators are actually syntactic sugar that is called after the function is declared, producing decorated_func and replacing the reference to the func symbol with decorated_func. each subsequent call to the func function actually calls decorated_func (and this is important; after decoration, each call actually calls decorated_func).

>>> def decorator(func):
...   def decorated_func():
...     func(1)
...   return decorated_func
... 
# Called at the time of declaration
>>> @decorator
... def func(x):
...   print x
... 
decorator being called 
# Using the func() function actually uses the decorated_func function
>>> func()
1
>>> func.__name__
'decorated_func'

If you want to ensure that the function name of the returned decorated_func is the same as the function name of the func, you should add decorated_func.name = , before the decorator function returns decorated_func. Additionally, the functools module provides the wraps decorator, which accomplishes this action.

The operation of #@wraps(func) is equivalent to the operation of
# Before returning decorated_func, execute the
#decorated_func.__name__ = func.__name__
#func passed in as a decorator argument.
#decorated_func is then passed as an argument to the function returned by wraps
>>> def decorator(func):
...   @wraps(func)
...   def decorated_func():
...     func(1)
...   return decorated_func
... 
# Called at the time of declaration
>>> @decorator
... def func(x):
...   print x
... 
decorator being called 
# Using the func() function actually uses the decorated_func function
>>> func()
1
>>> func.__name__
'func'

The beauty of local variables in decorator functions

Because of the nature of closure (see (1) for details in the closure section), variables declared by decorator are referenced by decorated_func.func_closure, so the local variables of the decorator method are not reclaimed even after the call to the decorator method is over, and so you can use the local variables of the The local variables of the decorator method can be used as counters, caches, and so on.

It's worth noting that if you want to change the value of a variable, it must be a mutable object, so even if it's a counter, it should be implemented as a list. And declaring a function once calls the decorator function once, so the counters of different functions don't conflict with each other, for example:

#!/usr/bin/env python
#filename 
def decorator(func):
  #Note the use of mutable objects here
  a = [0]
  def decorated_func(*args,**keyargs):
    func(*args, **keyargs)
    # Because closures are shallow copies, if it's an immutable object, the symbols will be cleared after each call, resulting in an error
    a[0] += 1
    print "%s have bing called %d times" % (func.__name__, a[0])
  return decorated_func
@decorator
def func(x):
  print x
@decorator
def theOtherFunc(x):
  print x

Here we start writing the code:

#coding=UTF-8
#!/usr/bin/env python
#filename 
import time
from functools import wraps
def decorator(func):
  "cache for function result, which is immutable with fixed arguments"
  print "initial cache for %s" % func.__name__
  cache = {}
  @wraps(func)
  def decorated_func(*args,**kwargs):
    # The name of the function as key
    key = func.__name__
    result = None
    # Determine if a cache exists
    if key in ():
      (result, updateTime) = cache[key]
      #Expiration time fixed at 10 seconds
      if () -updateTime < 10:
        print "limit call 10s", key
        result = updateTime
      else :
        print "cache expired !!! can call "
        result = None
    else:
      print "no cache for ", key
    # If it expires, or if there is no cache to call the method
    if result is None:
      result = func(*args, **kwargs)
      cache[key] = (result, ())
    return result
  return decorated_func
@decorator
def func(x):
  print 'call func'

Randomly tested it and it's basically fine.

>>> from decorator import func
initial cache for func
>>> func(1)
no cache for func
call func
>>> func(1)
limit call 10s func
1488082913.239092
>>> func(1)
cache expired !!! can call
call func
>>> func(1)
limit call 10s func
1488082923.298204
>>> func(1)
cache expired !!! can call
call func
>>> func(1)
limit call 10s func
1488082935.165979
>>> func(1)
limit call 10s func
1488082935.165979

Above this python decorator - limit the number of function calls (10s call once) is all that I have shared with you, I hope to be able to give you a reference, and I hope you will support me more.