Search code examples
pythondecoratorpython-decorators

Use decorators to call function without arguments


In an effort to make my code clearer for (mostly) myself to read, I was attempting to use decorators to put most of the function arguments inside @decorator(args), and then calling the function without arguments. This is my current code:

def dec1(*args, **kwargs):
    def dec2(func):
        return func(*args, **kwargs)
    return dec2

@dec1(1, 2, 3)
def func1(val1, val2, val3):
    print(val1)
    print(val2)
    print(val3)

if __name__ == "__main__":
    func1()

However, it is reporting this (essentially running the code using the decorator, but not the second function call):

1
2
3
Traceback (most recent call last):
  File "/home/shadowrylander/decorator_test.py", line 13, in <module>
    f1()
TypeError: 'NoneType' object is not callable

What I am trying the accomplish is similar to what the Click library does (defining hello() with arguments, then later calling it with none):

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
              help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo('Hello %s!' % name)

if __name__ == '__main__':
    hello()

I would be very grateful if anyone could help me create a decorator similar to this, and I apologize if this has already been asked and/or answered before; I either could not understand them properly or could not find the question!
Thank you kindly for the help!


Solution

  • In dec2 you are returning the result of calling func1 with the specified arguments, which is not what you want.

    What you want is returning a function f which calls func1 with the specified arguments, that is:

    def dec1(*args, **kwargs):
        def dec2(func):
            def f():
                return func(*args, **kwargs)
            return f
        return dec2
    

     

    A more detailed explanation:

    Remember that the decorator syntax:

    @dec1(1, 2, 3)
    def func1(val1, val2, val3):
        ...
    

    is syntactically equivalent to:

    def func1(val1, val2, val3):
        ...
    func1 = dec1(1, 2, 3)(func1)
    

    so the result of dec1(...) (dec2) is called with the decorated function (func1) as argument at the time you decorate the function. So you don't want dec2 to do anything but return a function that will do something later when it's called.