python decorator TypeError missing 1 required positional argument

蓝咒 提交于 2020-07-18 12:50:30

问题


I'm trying to write a decorator to repeat an erroring function N times with increasingly sleeping times in between. This is my attempt so far:

def exponential_backoff(seconds=10, attempts=10):
    def our_decorator(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
    return our_decorator

@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()

I keep getting the error:

TypeError: our_decorator() missing 1 required positional argument: 'func'

回答1:


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.




回答2:


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>



回答3:


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 functools.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 !



来源:https://stackoverflow.com/questions/51891951/python-decorator-typeerror-missing-1-required-positional-argument

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!