Reputation: 321
I have prepared some function decorator with default argument passed from decorator point of view:
def decorator_wraper(max_repeating=20)
def decorator(function):
def wrap_function(*args, **kwargs):
print(f"max_repeating = {max_repeating}")
repeat = 0
while repeat < max_repeating:
try:
return function(*args, **kwargs)
break
except Exception:
pass
repeat += 1
return wrap_function
return decorator
It works ok, so if some decorated function fails - decorator allows to repeat the function until success or repeat >= max_repeating.
But what if I will need to change the default max_repeating from decorated function point of view?
Here I have two decorated functions:
@decorator_wraper(5)
def decorate_me_with_argument(max_repeating=10):
print('decorate_me_with_argument')
@decorator_wraper()
def decorate_me_with_default():
print('decorate_me_with_default')
calling:
decorate_me_with_argument() # max_repeating should be 5 cause while decorating the function I have passed 5.
decorate_me_with_default() # max repeating should be 20 cause default for the decorator is 20.
And what I want to achieve:
decorate_me_with_argument(3) # max_repeating should be 3
decorate_me_with_default(8) # max repeating should be 8
Maybe is there any simple method to solve something like this?
Upvotes: 2
Views: 2501
Reputation: 121975
The other way to do this is to expose the number of repeats on the wrap_function
:
def decorator_wrapper(max_repeating=20):
def decorator(function):
def wrap_function(*args, **kwargs):
print(f"max_repeating = {wrap_function.max_repeating}")
for _ in range(wrap_function.max_repeating):
try:
return function(*args, **kwargs)
except Exception:
pass
wrap_function.max_repeating = max_repeating
return wrap_function
return decorator
That way you don't change the interface of the wrapped function
, but you can still change the repeat limit after the initial decoration:
>>> @decorator_wrapper()
... def bad_func():
... print("in bad func")
... raise Exception("oh no!")
...
>>> bad_func.max_repeating
20
>>> bad_func.max_repeating = 3
>>> bad_func()
max_repeating = 3
in bad func
in bad func
in bad func
Adding arbitrary attributes to wrap_function
would also allow you to provide an inline API for calling the wrapped function with a different number of retries, e.g. something like:
bad_func.retry_times(3)()
Again bad_func.retry_times(3)
is a drop-in replacement for bad_func
as it returns a function that accepts exactly the same arguments, rather than adding to (and risking clashing with) the wrapped function's existing parameters.
Note you should probably raise the error if the last retry fails, or it will get lost entirely:
def retry(times, func, *args, **kwargs):
for _ in range(times):
try:
return func(*args, **kwargs)
except Exception as exc:
exception = exc
raise exception
exception = exc
is needed because except-clause deletes local variable.
Upvotes: 2
Reputation: 1
Following is similar to what I have in my project, the kwargs
param takes all what are passed from the decorated function:
def decorator_wraper(func):
def wrap_function(max_repeating=20, *args, **kwargs):
if kwargs.get('max_repeating'):
max_repeating = kwargs['max_repeating']
print(max_repeating)
# TODO
return func(*args, **kwargs)
return wrap_function
@decorator_wraper
def decorate_me():
pass
decorate_me() # should print 20
decorate_me(max_repeating=10) # should print 10
Upvotes: -1
Reputation: 1015
Make the first argument for wrap_function
the number of repeats with the default value of max_repeats
specified in the decorator.
def decorator_wraper(max_repeats=20):
def decorator(function):
def wrap_function(max_repeating=max_repeats, *args, **kwargs):
print(f"max_repeating = {max_repeating}")
repeat = 0
while repeat < max_repeating:
try:
return function(*args, **kwargs)
except Exception:
pass
return wrap_function
return decorator
@decorator_wraper(5)
def decorate_me_with_argument():
print('decorate_me_with_argument')
@decorator_wraper()
def decorate_me_with_default():
print('decorate_me_with_default')
decorate_me_with_argument() # max_repeating = 5
decorate_me_with_default() # max_repeating = 20
decorate_me_with_argument(3) # max_repeating = 3
decorate_me_with_default(8) # max_repeating = 8
Upvotes: 2