None as a Default Parameter
00:00
Welcome back. In this video, I’ll tell you about using None
as a default parameter. The strongest argument in favor of using None
as a default parameter is that it avoids the need to use a mutable data type as a default parameter.
00:13
That’s a bit of a mouthful but it’s easier to understand if I show you with an example. Here we are in the REPL. I’ll show you why using None
as an input parameter is a good idea by showing you what happens when you don’t use None
as an input parameter. So, first of all, I’m going to create a function and I’ll call it bad_function()
.
00:42
So, here’s my function. It takes in two parameters. One is an element and the other is an optional starter_list
. Then, what it does here is it appends the new element to the list.
00:53
Next, there’s a print()
statement whose purpose I’ll show you in a minute. And finally, I return the starter_list
. Okay, let’s try playing around with this. I’m going to start by creating a list
01:05 and then I’ll test out my function by adding something to this list.
01:11
So, this has added 4
to my list, so it used to have [1, 2, 3]
, now it has [1, 2, 3, 4]
. Okay, that looks good, and it shouldn’t be too surprising.
01:21
But watch what happens if I call on this function without passing a starter_list
, so if I say something like bad_function(5)
, and only pass 5
as a parameter?
01:31
So in this case, we created a new list and added 5
to it, and you can see that the ID
is different from the previous list. Okay, let’s call a function again and add the number 6
, and here is the issue. If you look at the ID
, this is the same as the one we had when I called the function just before. The issue here is that since I’m using a mutable object, it’s being recycled rather than restarted each time.
01:58
So when I call the function again, I’m working with the same object instead of having a fresh one. So, how can we get around this? Well, I’ll show you by creating a new function. At first, good_function()
looks a lot like bad_function()
, but the key difference is here. Rather than setting the starter_list
to an empty list, like we did up here, in this case, we’re setting it equal to None
.
02:21
And this time around, the first thing we’ll do is we’ll check if starter_list
is actually None
this time that the function is being called.
02:28 And if that’s the case, then we create an empty list,
02:33
and the following lines are just like they were above. So we append()
,
02:39
we print()
this just to confirm that we’re using different starter lists, and then we return our starter_list
. Okay, let’s try running this, first with our list we had already created. This time I’m going to try adding a letter.
02:55
And that’s working, and you can see that the ID
is the same we had up here. Okay, let’s try calling good_function()
without passing a list.
03:06
Okay, so here we go. That created an empty list and added 2
to it. Let’s call it again with a different number. And this time, rather than having a sticky list, we are getting a new list each time. You can see this, because the second time we call this, there’s only one number.
03:22
We didn’t append()
to the same list that had the 2
, and also the ID
s are different, so that’s working. Great. Next, I’ll show you how you can use None
as an input parameter.
03:32
This requires a little bit of tweaking. What I’ll do, first of all, is I’m going to create a class and this class will help regulate the behavior of my function when it receives None
as an input. Let me show you what I mean.
03:45
First of all, let’s create this class, and this doesn’t really do anything. All it does is pass
. Next, I’m going to create good_function()
again, but with a few tweaks, and the first of these is that I’m going to set a default value for new_elem
, and that value is DontAppend
. I’ll add what I had above here again, so this check whether or not starter_list is None
.
04:12
And if it is None
, then let’s create an empty list. Now we’ll do something a little bit different. I’m going to check if new_elem is not DontAppend
, and if it isn’t, then what I’ll do is I’ll append()
. Okay, now we can return starter_list
, and let’s try running this.
04:33
So first of all, I’m going to run it only passing my list, the starter_list
from above, and that was added to an empty function, so that’s working. Now let’s see what happens if I add None
to it.
04:47
And you see that what happens this time is it accepted None
as an input value and it appended it to the list, so that was working.
04:56
So, the key things to remember here are that you can use None
as a default parameter when using mutable data types would result in strange things happening, or unwanted things happening, and also that if you need to be able to add None
as a valid input to something, then you can create a class that you can use as a signal not to accept an input. In this way, you free None
to be used as an input if it comes up. Okay.
05:19
So, another place where you might encounter None
is in tracebacks, and that’s what we’re going to be looking at in the next video: how to deal with None
in tracebacks. See you there!
Bartosz Zaczyński RP Team on Aug. 3, 2020
Default function arguments are somewhat tricky in Python. The rule of thumb is to never use mutable types such as lists as default arguments because they create a kind of global variable that gets reused across function calls.
Python instantiates default argument values when it reads a function definition. That happens once when the interpreter loads the file rather than every time a function is called. That’s the most confusing part. Subsequent function calls won’t result in creating new lists but will resue the one already available.
Creating a new list manually within the function body fixes the problem. However, for immutable types such as strings and numbers, that would be unnecessary.
Pp VV on Oct. 27, 2020
Around 4:30: If you remove DontAppend
as a default for new_elem
, then the behavior is still the same. None
is still appended to starter_list
.
def good_function2(new_elem, starter_list=None):
def good_function(new_elem=DontAppend, starter_list=None):
Both examples above append None
to the list.
Become a Member to join the conversation.
dwalsh on July 29, 2020
At around 1:30 I see the following steps:
starter_list = [1,2,3]
bad_function(4,starter_list)
-> returns[1,2,3,4]
bad_function(5)
-> returns[5]
bad_function(6)
-> returns[5,6]
Christian explains the
starter_list
is being reused in steps 3 and 4 with the same memory id. Why did this reuse not occur between steps 2 and 3 then or put another way why did bad_function in line 3 not return[1,2,3,4,5]
. I am thinking that whenbad_function
is run the starter_list resets to[]
as the function defines the default that way. The video states it’s being reused in step 4 though. Why did this behavior not occur at step 3 then with the previous return ofstarter_list
? Hope this question makes sense.