Never Use [ ] as a Default Argument in Python...

NeuralNine · Intermediate ·⚡ Algorithms & Data Structures ·3mo ago

Key Takeaways

The video discusses the pitfalls of using mutable objects as default arguments in Python functions, demonstrating how this can lead to unexpected behavior and providing best practices for avoiding such issues.

Full Transcript

What is going on guys? Welcome back. In this quick video today, we're going to learn why we shouldn't pass lists or any mutable Python objects for that matter into Python functions as default arguments. This is just a small quirk of the Python programming language, but I think many beginners don't know about it and it can lead to unexpected problems. So, if you like this video, let me know by hitting a like button and subscribing, but now let us get right into it. All right, so let us jump right into the code. I'm going to open up my terminal and navigate to my tutorial directory. The only thing that we're going to need for this video today is a main.py file, no external packages, just core Python. So, I'm going to create it here in Neovim, main.py. And the concept is quite simple and straightforward. In Python, we can define functions like this. Let's call this my function and we can pass parameters. And with these parameters, we can do certain things. For example, I can just print the parameter that I passed, then I can call my function and pass anything to it like hello or a number like 10, for example. Then I can run this and I just get the print statement. Now, what we can also do with functions is we can provide default parameters. I can say that a parameter is equal to hello, for example, by default and even if I don't pass anything, it's going to print hello. And if I pass something, it's going to print whatever I pass. So, that is a default parameter. All this is fine and we can even pass different objects like empty lists, for example, or also lists with content. That is not an issue, we can do that. I can, for example, pass an empty list or a list with hello as a string in it and this will also be printed. So, where exactly is the problem? Why can I not pass just lists to Python functions? To understand this better, let's take a look at this example here. Quite simple, we have a function called create player. All it does is it takes a name as a parameter and an inventory as a parameter, it defaults to an empty list. And what we can do here is we always append a starter weapon to the inventory of the player, and then we return just a dictionary with name and inventory. That's the function, and then we call it twice. We create player one, Alice, player two, Bob, and we add to Alice's inventory a sword and to Bob's inventory a potion. Then we just print the two players. Now, someone who doesn't know about this behavior and doesn't know why it's not a good idea to pass mutable objects to Python functions might not understand what will happen here, and will be surprised that when you run this, when you save this and run this, both people, both players, have the same inventory and have two starter weapons. So, as you can see, we have Alice, we have Bob, and they both have starter weapon, starter weapon, sword, and potion. So, they have the same inventory, and the inventory contains two times the starter weapon. Why is this the case? The reason this happens is because the initialization of the empty list happens when we write it. By typing square brackets like this, what we do is we create the instance of the list here in the function signature. It's not created on every function call, it's created once when we define the function, and then the same object is being reused all the time. So, when I say inventory.append, I take the list that was created here in the function definition, and I append a starter weapon. So, basically, this empty list is created the first time I create a player, it appends starter weapon, the second time it appends it again to the same list because we don't run this initialization twice, we only run it once, and then when we append something to the inventory, we also append it to the same list. And by the way, the same behavior also happens when we do it with dictionaries or sets. For example, here we have create player, instead of passing an inventory, we pass a config dictionary. So, we just say create player Alice, create player Bob, and then we say, "For Alice, I want to set the difficulty to hard, and for Bob, I want to set the volume level to 80. And if I then print the two players, you will see that they both share the same config object, difficulty hard, and volume level 80. They don't have their independent configs. We're defining a dictionary object here when we define the function, we're initializing it, and then we're just working on the same object. Here also now another example with a set. We have permissions here, and we just add permissions like read and write. And when we do it like this, we again get right and read for both people, even though we appended one or we added one to player one and another one to player two, but it's the same object. And this is actually the case, as I said, for all mutable objects. So, you can even define your own class config. You can define an init method with difficulty, volume level, a representation dunder to print the information, and then we can pass a config object by initializing it here. Again, it's initialized at definition time of the function. So, when we define the function, we're initializing a single config object, and this object is being reused all the time. So, if I run this, again, we have the same config for both players. So, let us now move back to the initial example here, the inventory where we had the list as a default argument. How are we supposed to do this properly? Because, of course, there are use cases where you want to have an empty list as a default value. How you do this is, first of all, you replace this value here by none. So, this would be not initialized. It makes it an optional argument. You don't need to pass anything, but if you don't pass anything, it's going to default to none. And what you do then is, in the function, you check if the inventory is none, and if it is none, you do the initialization. So, it happens on function call time, not on function definition time. So, I can say inventory is equal to an empty list. Now, when I run this, you will see that Alice has the starter weapon and the sword, and Bob has the starter weapon and the potion, exactly as we expected. However, this is also not necessarily the the approach, because what I can do now, for example is I can say empty inventory and say that an empty inventory looks like this or I can maybe say default inventory and add some items in there which would be probably an actual use case and then I want to pass this as a default argument. So I would say for example empty inventory pass it to Alice and pass it to Bob and you can again imagine this to be an inventory with a default weapon, default armor or something in there. And now when I run this you will see that I get the same problem as before. Starter weapon, sword and potion because now of course the inventory, the empty inventory is not initialized or defined here at the function definition time but I still work with the same empty inventory every time and even if I have something like default armor in here when I run this you will see that it leads to the same problem. So that is not possible. So to make this even safer what you could do is you could add one more line which is inventory is equal to inventory.copy. What happens here is that we create a copy of the inventory instead of using the object that was passed to the function directly. So now I can run this and we will see we have default armor, starter weapon and sword for Alice and default armor, starter weapon and potion for Bob. So yeah, that's the reason why you should never pass mutable objects to Python functions as default arguments. If you want to play around with this in a very simple way just open up the Python shell, define a function my function, give it a default argument like param that is an empty list then just say param.append and then for example a number and print param. Now every time you call this function you will see what the problem is. You're basically getting more and more values in this list because you're reusing the same list. And that's what you want to avoid. You can do it like this and like this to be safe. So that's it for this video today. I hope you enjoyed it and hope you learned something. If so let me know by hitting a like button and leaving a comment in the comment section down below. Also in case you're interested, on my website you will find a services tab and a tutoring tab that you can contact me if you need help with a project, if you need a freelancer or consultant. You will find the links for LinkedIn and email at the bottom of the pages. Besides that, don't forget to subscribe to this channel and hit the notification bell to not miss a single future video for free. Other than that, thank you very much for watching. See you in the next video and bye.

Original Description

💻️ Need some help with a project or some consulting? Contact me here: https://www.neuralnine.com/services 🐍 The Python Bible Book: https://www.neuralnine.com/books/ 💻 The Algorithm Bible Book: https://www.neuralnine.com/books/
Sign in to unlock AI tutor explanation · ⚡30

The video teaches viewers about the dangers of using mutable objects as default arguments in Python and how to avoid these issues by using immutable objects or None as default arguments. It provides practical examples and best practices for writing safer Python code.

Key Takeaways
  1. Define a function with a mutable object as a default argument
  2. Observe the unexpected behavior caused by the mutable object
  3. Replace the mutable object with an immutable object or None
  4. Test the function to ensure the expected behavior
💡 Using mutable objects as default arguments in Python can lead to unexpected behavior and bugs, which can be avoided by using immutable objects or None as default arguments.

Related Reads

Up next
Find the Median in a Data Stream (Two Heaps Trick)
KodeKloud
Watch →