I would like to write a python decorator so that a function raising an exception will be run again until either it succeeds, or it reaches the maximum number of attempts before giving up.
Like so :
def tryagain(func): def retrier(*args,**kwargs,attempts=MAXIMUM): try: return func(*args,**kwargs) except Exception as e: if numberofattempts > 0: logging.error("Failed. Trying again") return retrier(*args,**kwargs,attempts=attempts-1) else: logging.error("Tried %d times and failed, giving up" % MAXIMUM) raise e return retrier
My problem is that I want a guarantee that no matter what names the kwargs contain, there cannot be a collision with the name used to denote the number of attempts made.
however this does not work when the function itself takes attempts
as a keyword argument
@tryagain def other(a,b,attempts=c): ... raise Exception other(x,y,attempts=z)
In this example,if other is run, it will run z times and not MAXIMUM times (note that for this bug to happen, the keyword argument must be explicitly used in the call !).
Advertisement
Answer
You can specify decorator parameter, something along the lines of this:
import logging MAXIMUM = 5 def tryagain(attempts=MAXIMUM): def __retrier(func): def retrier(*args,**kwargs): nonlocal attempts while True: try: return func(*args,**kwargs) except Exception as e: attempts -= 1 if attempts > 0: print('Failed, attempts left=', attempts) continue else: print('Giving up') raise return retrier return __retrier @tryagain(5) # <-- this specifies number of attempts def fun(attempts='This is my parameter'): # <-- here the function specifies its own `attempts` parameter, unrelated to decorator raise Exception(attempts) fun()