python decorator TypeError missing 1 required positional argument

Understand what decorator is:

@exponential_backoff
def test():
    pass

equals to:

def test():
    pass

test = exponential_backoff(test)

In this case, test is def our_decorator(func):. That's why you get TypeError when calling test().


So further:

@exponential_backoff()
def test():
    pass

equals to:

def test():
    pass

test = exponential_backoff()(test)

In this case, now test is what you need.


Further, functools.wraps helps you to copy all properties of original function to decorated function. Such as function's name or docstring:

from functools import wraps

def exponential_backoff(func):
#   @wraps(func)
    def function_wrapper(*args, **kwargs):
        pass
    return function_wrapper

@exponential_backoff
def test():
    pass

print(test)  # <function exponential_backoff.<locals>.function_wrapper at 0x7fcc343a4268>
# uncomment `@wraps(func)` line:
print(test)  # <function test at 0x7fcc343a4400>

You should be using:

@exponential_backoff()
def test():
    ...

The overall decorator is not designed to have arguments be optional, so you must provide () when using it.

If want an example of how to make decorator allow argument list be optional, see:

  • https://wrapt.readthedocs.io/en/latest/decorators.html#decorators-with-optional-arguments

You might also consider using the wrapt package to make your decorators easier and more robust.


Either you go for the solution provided by @Graham Dumpleton or you can just modify your decorator like so:

from functools import wraps, partial

def exponential_backoff(func=None, seconds=10, attempts=10):
    if func is None:
        return partial(exponential_backoff, seconds=seconds, attempts=attempts)

    @wraps(func)
    def function_wrapper(*args, **kwargs):
        for s in range(0, seconds*attempts, attempts):
            sleep(s)
            try:
                return func(*args, **kwargs)
            except Exception as e:
                print(e)
    return function_wrapper

@exponential_backoff
def test():    
    for a in range(100):
        if a - random.randint(0,1) == 0:
            print('success count: {}'.format(a))
            pass
        else:
            print('error count {}'.format(a))
            'a' + 1

test()

EDIT My answer was not entirely correct, please see @GrahamDumpleton's answer which shows how to make my attempt of a solution viable (i.e. this link). Fixed it now, thank you @GrahamDumpleton !