Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Decorating Functions With Arguments

This lesson will show you how to use the *args and **kwargs syntax to modify decorators to accept any number of arguments.

00:00 What if you need to pass arguments into your decorated functions? Let me show you that next. I’ll have you keep using decorators.py. Back here in the REPL, from your module decorators, import do_twice. To decorate a new function, start with @do_twice, then define like normal.

00:27 But this time, let’s say we’re defining greet() with the argument name, and then it will use an f-string, with the curly braces, to print that name.

00:40 So here’s greet. It looks like it’s been properly decorated. In this case, you pass in "World", so it will say "Hello World"

00:52 uh oh, it looks like we found an error. The wrapper_do_twice() function that’s part of the decorator that you’ve imported takes zero positional arguments, and you gave it one.

01:04 Could you modify your decorator @do_twice so that it could take an argument? Sure. This may get a little messy though. So back inside decorators.py, you could create an argument named one_arg and pass it into the function—in this case, both function calls—and save. In order for you to reuse this decorator,

01:35 you’ll need to exit the REPL and then re-import it.

01:43 Let’s start my REPL again.

01:54 And redefine your function, greet(), printing f"Hello {name}". Okay, so here’s greet. Pass into greet(), "World" when you call it—great! That worked fine.

02:14 What about if you create using the same decorator @do_twice your old friend the function say_whee()? Oh, don’t forget the exclamation point.

02:28 Okay. So here’s say_whee—looks good. Let me have you call it. So now it’s saying it’s missing one required positional argument, but in the case of greet()

02:46 give it an argument—that works fine. I’m going to go back in time just one moment and show you something. When calling say_whee() in my REPL bpython, it shows that say_whee() takes one argument, which isn’t true. When it was defined, it accepted no arguments.

03:10 That’s very interesting. That’s something the decorator is doing. And since this decorator is set up to take one argument—that’s the name of the argument—it’s applying that to this function also.

03:25 Is it possible back here in our decorator to be flexible? To accept one, two, three—multiple arguments, and also potentially except none? Sure! There’s a standardized way to do that. Let’s rewrite this.

03:46 You’re going to add *args and **kwargswhich stands for arguments and keyword arguments—to your decorator.

04:00 And whatever arguments or keyword arguments that are passed through our wrapper here will get passed when those functions are called. So, let’s save. Okay. So the trick again, back here in our REPL, is I do need to exit and start my REPL again.

04:22 Okay. Now, if you import, from decorators import do_twice, can you define @do_twice decorating greet()?

04:36 Here’s greet. It looks good. Notice how, as I type this, it shows greet with *args and **kwargs available.

04:46 Let’s greet the world. Great! That worked fine. And what if you apply @do_twice—the decorator—to a simpler function, like say_whee(), that takes no arguments—will that work?

05:03 So, here it is. Yep! It looks good. And again, if I freeze for a second, you’ll see that say_whee(), which by definition didn’t take any arguments, shows *args and **kwargs. Not that that function, which the way it was defined, can actually accept any. The decorator would accept arguments, and then pass them into our function, and that’s where it would fail.

05:32 And now call it without any arguments. So now, functions that have no arguments can be decorated with @do_twice, and functions that do have arguments passed in can be decorated with that same decorator, because you have added *args and **kwargs and are passing those into our functions.

05:59 That’s the flexibility of using them. Great! Next, let’s return values from decorated functions.

Avatar image for Vanam

Vanam on May 8, 2019

I think in the example, we can have the wrapper to accept only args, def wrapper(*args): With this also, we can pass with or without arguments while calling the decorated function (Please correct me if i am wrong)

Avatar image for Chris Bailey

Chris Bailey RP Team on May 8, 2019

Hi Vanam, You are correct. If you don’t think your wrapped functions will do anything with keyword arguments, then you could use (*args) only. The method shown will allow either type, and will not have issues if you only use arguments and not keyword arguments (or vice versa). It would, as a tool, be more flexible.

Avatar image for grzegorzmalinowski

grzegorzmalinowski on Jan. 8, 2020

Is it possibly inside the wrapper to pass new arg vaalue to the function to be decorated?

Avatar image for Chris Bailey

Chris Bailey RP Team on Jan. 8, 2020

Hi @grzegorzmalinowski, I’m not sure I completely understand your question. When you are calling the decorated function as in the example, you are passing arg values to the function, or not if desired. You are providing the argument at the time of calling the function, which is one interpretation of “new”. It is possible to have structured the decorator to handle the arguments or keyword arguments in unique or separate ways also. Meaning do one thing with arguments and another with the keyword arguments. But I don’t think I’m catching your meaning.

Avatar image for grzegorzmalinowski

grzegorzmalinowski on Jan. 8, 2020

Hi Chris, thank you for the answer. Since I’ve just started my journey with decorators, it might be that I was imprecise in fact. Please see my code. I’d like to change alpha (from predefined scalar to array/or any new value).

@opt_alpha
def update_weights(gradient_type='GD'):

    def MSE_GD(aug_data, response, weights, prediction, alpha=learning_rate):
        if np.ndim(alpha) != 0:
            alpha = alpha[:,np.newaxis]

        gradient = 2/response.size*((prediction - response)@aug_data)
        direction = -1*gradient
        weights = weights + alpha*direction
        return weights

    if gradient_type == 'GD':
        return MSE_GD
def opt_alpha(function):
    alpha = np.array([0,1,2])
    def weights_wrapper(*args, **kwargs):
        result = function(*args, **kwargs)
        return result
    return weights_wrapper
Avatar image for Chris Bailey

Chris Bailey RP Team on Jan. 9, 2020

Hi @grzegorzmalinowski, I think you would need to add the alpha keyword argument to your decorator “result”, after **kwargs. Please excuse my simplifying your function.

def opt_alpha(function):
    alpha = [0,1,2]
    def weights_wrapper(*args, **kwargs):
        result = function(*args, **kwargs, alpha=alpha)
        return result
    return weights_wrapper

@opt_alpha
def print_alpha(response=25, weights="normal", alpha=0):
    print(alpha)
    print(response)
    print(weights)

When calling you would get.

>>> print_alpha()
[0, 1, 2]
25
normal

The alpha argument will be passed to the function. But to take this a bit further have you looked at the original article that Geir Arne wrote, that my course is based on? Primer on Python Decorators, there is a section on Decorators with Arguments. It discusses when “decorating” a function, you could add an argument to the decorator, which could be the direction you are looking for. I hope this helps.

Become a Member to join the conversation.