tomm
tomm

Reputation: 321

Passing arguments to decorator from decorated function

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

Answers (3)

jonrsharpe
jonrsharpe

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

DuyAnh Nguyen
DuyAnh Nguyen

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

big_bad_bison
big_bad_bison

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

Related Questions