Locked learning resources

You must own this product to watch this lesson.

Locked learning resources

You must own this product to watch this lesson.

*args and **kwargs

00:00 Hey, how’s it going? Today I’m going to talk about the *args and **kwargs, or keyword arguments. And this is, again, a feature in Python that can be a little bit arcane if you’re seeing it for the first time, just because it looks a little bit weird and it has this strange-sounding name, right? “Argh!” and “Kwargh!” And what is this even good for?

00:27 So, I want to talk about how these things work, what they’re good for, and how you can use them to write better programs to write more Pythonic Python, and just become a better Python developer.

00:43 All right, so how do these guys work? How do these *args and **kwargs work? Let’s take a look at this simple example here. So, I’m defining this function foo() here that takes one required parameter, and then it also has these *args and **kwargs here.

01:02 And what it’s then going to do—it’s going to print all of these arguments, right? So if we have *args, we’re going to print them, if we have **kwargs, we’re going to print them, and we’re also going to print the required argument.

01:16 That is going to be our testbed to actually inspect what’s going on behind the scenes. So, first of all, what you can see here is that this function—because of this definition—it will require at least one argument that’s called required, but it can also accept extra positional and keyword arguments. Now, if we call this function with extra arguments, then *args will collect all of the non-named, or positional arguments, and **kwargs will collect all of the keyword arguments.

01:51 And this happens not because these parameters are called *args or **kwargs, but because of that little star operator (*), right?

01:59 It’s just sort of a naming convention to call them *args and **kwargs, and they’re kind of pirate-y and easy to remember, right? “Argh!” and “Kwargh!” Okay, so we have this foo() function now, and what I want to do now is call it with various combinations of arguments so that you can see what actually happens behind the scenes.

02:15 The first case is really easy. We’re just going to call foo() without any arguments, and then Python is going to complain that, “Hey, I need at least this one argument called required.” So, I guess that was kind of expected. Now, the second case is where we would call foo() with the required argument—I’m just going to put in 'hello'. And you can see here, okay, we get hello as the printout. It looks like we didn’t have any *args or **kwargs.

02:39 And now what I’m going to do next is I’m going to call foo() again. I’m still giving it this string here as the required argument, and then I’m adding a bunch more numbers here that are going to be the extra positional arguments. So when I call this, you can see here that it printed the *args here. All right, so now we had *args*args wasn’t empty.

03:07 But now it’s actually a tuple containing all of the extra positional arguments that were collected. And now we can go beyond that, and besides having these extra positional arguments, I’ve also added these two extra keyword arguments, right?

03:22 I’ve called them key1 and key2. Now, when I run this, you can see here that this result’s pretty much the same as before, but now we also have this dictionary here that contains all of the extra keyword arguments. So we didn’t—or the function didn’t really ask for any of these arguments to be passed in, but it was able to handle them, and the Python runtime collected all of the extra arguments inside this *args tuple and this **kwargs, or keyword args dictionary,

03:55 which means that now our function can use them for something useful. So with that method, you could create functions that react differently depending on how many or what kinds of parameters you pass in, which could be useful.

04:10 They also allow you some other techniques that I want to show you now. Okay, so the first technique that we can use these extra positional and extra keyword arguments for is that we can write wrapper functions that modify or, you know, do something to those arguments and then actually pass them on to the original function. So here I’ve redefined foo() again, and now foo() is actually calling bar(), but before it calls bar() with the same arguments, it makes some modifications to *args and **kwargs.

04:46 So, you can see that here where with **kwargs it’s really easy because this is just a dictionary, so you can you know, mutate it in place. And I just added this 'name' key to **kwargs. With *args, because it’s a tuple, you need to create a new object and then use that because tuples are immutable, so you can’t add stuff to them. So here, what I’m doing—I’m just taking *args and I’m adding this 'extra' string here.

05:15 I stored that as new_args, and then when I call the original function, or the wrapped function, I pass all of this stuff to bar()—the original x and then the new_args and the modified **kwargs, using this * and ** operator.

05:33 This gives us a way to wrap an existing function and actually make some modifications to the arguments that are getting passed in, and then still kind of reassembling everything and calling the original function. The nice thing about that is that I don’t really have to know what *args or **kwargs looks like, right?

05:55 I can just kind of take whatever I’m given and then—or, I can write a function that takes whatever it’s given, and then it can make modifications where it wants to, and then pass that on.

06:07 Now, this sounds a little bit dangerous and it totally is because it can definitely create a maintenance nightmare, but in some cases when you’re wrapping some external library, this could be the way to go, right?

06:17 So, I just wanted to point that out. But in general, you maybe want to be careful with that. All right. Here’s another example. So, this method can also be really helpful for subclassing.

06:27 Let’s say you want to extend the behavior of a parent class without having to replicate the full signature of its constructor in the child class—for example, if you’re working with an API that changes outside of your control.

06:41 This is the ever-popular Car class that takes a color and the mileage, and then I’ve extended that with the AlwaysBlueCar class that is going to call the parent class—so, it’s going to call the parent class constructor with this super().__init__()and then it’s just going to override the .color.

07:02 And now, you can see here the way this is implemented I didn’t actually duplicate the information up here—the parameters up here, so I didn’t call this color and mileage and so on—but I just said, “Hey, give me everything that was there previously,” and then I took those and forwarded it to the original constructor up here, and then I just went in and kind of overrode the .color to be 'blue'.

07:27 Now, while this is a super powerful technique for wrapping these classes, or changing the behavior of a class that you don’t want to replicate large portions of it—maybe you just want to selectively override something, if it’s not a class that you have control over because it’s a third-party class—then this could be a viable method just to kind of patch that information in.

07:51 But on the other hand, there are some downsides associated with it as well. So, one downside is that now this AlwaysBlueCar has a really unhelpful signature in its constructor, right? It just says *args and **kwargs, which doesn’t really tell you the kind of arguments it expects, so you would have to fix that with a good docstring and say “Hey, you really want to look up the Car class. This is just a thin wrapper around it.” Be careful with that.

08:17 Now, the most likely scenario where you would use a technique like that—if you want to override some external class or some behavior in an external class which you don’t control, which is a pretty good motivation, but you know, it’s always dangerous territory to do that, right?

08:34 Or, otherwise, probably it’s going to be you who’s going to be screaming “Argh!” and “Kwargh!” if you’re doing that too much. So, I would definitely be careful with this because it’s a super—potentially—helpful technique, but on the other hand, it could also be extremely dangerous.

08:48 There’s actually a third way where you can use this technique for great effect, and that’s when you’re writing decorators, which are basically wrappers around other functions. And they’re—pretty much all of the code examples you see out there—they’re going to use this technique to wrap a function and just forward arguments to it. All right, so this is where you can apply that method as well.

09:11 So, this is a really powerful feature in Python, and this is definitely something that I would play with and explore if you want to become a better developer and make your Python code more clean and beautiful and more Pythonic, because it is used quite frequently. So, play with it and understand how it really works—that would be my recommendation. But of course, with a technique like that, it’s kind of this, “With great power comes great responsibility,” right?

09:39 So you want to make sure you balance that to keep your code explicit enough to kind of balance it with the need just to—well, you know, for your convenience versus the convenience of the future developer or your future self who’s going to be working with that code. So, again, I just wanted to mention that because it’s a tough choice to make and it’s definitely something that a great developer should be thinking about when they’re writing code.

You must own this product to join the conversation.