 Let's start it. Let's see how Dunder methods rule Python under the hood. My name is Rodrigo. That's my face. This is also my face, so you can find me online. And what I do is I work for Textualize on the textual framework. And I also like writing about Python over at that website. So teaching really is a passion of mine and that's why I came here last minute. And I'm taking the risk of, I don't know, making some mistake, but I'll be trying to share with you some of the things that I like about Python. So all the slides in the code that we will be going through are available at this repository, so don't worry about taking pictures. You can take pictures. You don't have to. And, yeah, let's get started. So the goal of this short talk is to answer the question what are Dunder methods. And the main key, yeah, the main key takeaway, so the key takeaway I want you to take from this is that they are not dark magic, all right? For some reason, some people call them magic methods, but they're not magic. Actually, they make a lot of sense. They're regular methods. They just do some interesting things. And I want you to understand what these interesting things are. So the proper name, if it's not magic methods, it's Dunder methods. And why is it Dunder? So what the hell does that mean? Dunder just stands for double underscore. So the Dunder methods are those methods that have the interesting looking names like Dunder init or Dunder stir. And there's plenty of them. There's too many to count. Okay? So there's a lot of them, and I'm not going to enumerate all of them because that's not the point. I just want you to familiarize yourself these methods that start with two underscores and end with two underscores. They just have the name like that because Python uses them for some special purposes. And we wouldn't want the user to create an init method with I and IT. And then we wouldn't want that to override whatever special method Python is waiting for. And that's why they have the double underscores to make it very, very unlikely that the unsuspecting user might overwrite those methods by mistake. So I want you to, I want us to start by taking a look at these two Dunder methods. So we're going to explore them a little bit so that you can see them in action and that you really understand the key points of this talk, which is they're not magic. They're regular methods. They do regular things. Okay? So if I manage to spell Python, everyone comfortable with this? What happens if I press enter? Can no one tell? Okay. So we're going to get a three. Okay. Fine. Is everyone comfortable with this? What do I get if I press enter? Okay. So everyone is comfortable with the line I just typed? You're sure? Because if you are, then you have, quote, unquote, you have to be comfortable with this. Who's uncomfortable with this? At least for now. Who's uncomfortable with this? Okay. I used to be uncomfortable with this, too. But it's fine because three dot underscore underscore str underscore underscore open and close parenthesis just gives me a string that looks like the number. Does this look reasonable to you? Because the objective of that Dunder method, what it does is it takes whatever object you have and turns it into a string so that it can be printed, for example. And so when I called print, what happened under the hood was Python took the three and it looked for a stir method. And if it finds a stir method, it calls it because a method is just a function, right? So you can call the method. It gets that thing back and then it shows it on the screen. So that's what Dunderster does. It's just that. Does this make sense to you? Or is this how, who feels this is weird? Wait, on one go, everyone may, okay, so then I think we're done for now. And we can all go to lunch, right? So that's the whole point of these Dunder methods. Dunder methods are regular methods. You can implement them yourself. And the point is there are special occasions that are well-defined, okay? You can read what those occasions are. There are special occasions in which Python calls those methods automatically for you. Because in, maybe easier if I look at here, yeah. So in this slide, I did not call stir automatically, but Python did, all right? So that's the thing that you have to bear in mind. There are going to be situations in which Python will call those Dunder methods for you. And that's why people call them magic. Because you don't call them explicitly Python does. But there are well-defined rules, so they're not magic. It's just the way it works. Let's take a look at another example. I have a list here. Right? So what do I get if I do my lists? Dunderster. Who can guess what's going to show up on the screen? Exactly. A string literal of the list. That's just it. Which is going to match what I get if I print my list. Right? That's the only purpose of Dunderster. It looks funky. What? What? No, no, no. It does, it gives you string first. And if it doesn't find Dunderster, it will look for Dunder wrapper. What it does is it calls wrapper on the elements themselves. So that's a very nice distinction. But thankfully, I didn't make a mistake yet. I'll make some mistake. It wasn't this one yet. Okay. But thank you, thank you. So, yeah, that's what's happening here. And actually, to bring your point across clearly, we were actually going to talk about that. And there's one thing we can do to make the distinction here. So if I do, I need to think for a second. So if we do, let's create a string, not this one. So my string is going to be my name because I don't have any imagination. Now, if I do, let me think, if I do string of my string, and if I do wrapper of my string, can you see that they look different? Can I? Yes, I can move up. I'm sorry for that. Can you see that they look different? One is just the string with seven characters, which composes my name. And the other is a string with nine characters. It's the string with the string inside. So they are different. Do we agree that they look different? Awesome. Because they do. Because if you don't agree with me, then there's nothing I can do for you. And these two built-ins are calling two different Dunder methods. All right? Who can guess what Dunder method is getting called by this function? We've seen it before. Dunderster. All right. So then I know it's dangerous to generalize over one single example, but let's do it anyways. Who can guess what Dunder method is getting called when this built-in is called? Dunder wrapper. Exactly. And so these two methods, they might seem like they do essentially the same thing, and they do similar things. They are supposed to build strings out of your object, but stir is supposed to be for pretty printing. Let's call it that. It's the nicest representation of your object. And wrapper, you can think of it as the debugging, quote, unquote, the debugging version of your representation. So when we do the next exercise, let's call it that, you will see what the main purpose of string and wrapper, like the distinction. You will see what the distinction is. And this also comes in inside of lists. So we will see that they are different, even though they look similar. So actually, no, I don't know why I'm checking my slides again. So let's actually go to an editor. And let's open a file. Let's call it person.py. And I want to, is this big enough for the people at the back? I can increase the font size. It's, okay, very nice. So who's comfortable with me typing this? Sorry, I know I asked the question, but I wasn't looking at you because I cannot, like, typing and talking at the same time is already hard enough, typing, talking and looking away, that would be insane. So who's comfortable with this? Which isn't helpful. Who's not comfortable with this? Okay, fair enough. So why are you, okay, so if maybe you're not too comfortable with this, are you comfortable or do you become more comfortable if then I add this line of code? And John Smith has already made an appearance in one of my tutorials later this, earlier this week, so this should be a familiar person. Now, I'm having issues with sizes. If I run this, can you tell the people that raise their hands? Can you see what's happening here? Or are you still uncomfortable? Okay, I see someone squinting, is it too small at the bottom? Okay. So essentially what I'm doing here is I'm defining a class person, right, so that I can create objects that are different people. For example, in here I want to create some Python thing, some Python object that represents John Smith. And what I use and what everyone uses when they're creating classes is this funny-looking method, underscore, underscore, init underscore, underscore, that looks very magical if you're just starting out programming and you're just starting out object-oriented programming. But this method is no weirder than something like this, then greet and then you just, I don't know, you print self.first.now this is the weird order, so hello someone, right? It's a regular method. Like these two methods, they're equally complicated. If you feel comfortable calling this one and implementing this method, there's, other than the weird looks, there's no reason to feel uncomfortable about this one because this method is something you can call. The deal is Python has a very specific rule that determines that the init method is going to be called for you. And what rule is that? That rule is, can I, sorry, wrong combo, when you type something like this, when you create an instance of a person, Python will create a new object. It will create an empty box. And then Python will give the empty box to some special method so that the empty box can be customized and become a person. And the method that's responsible for making that customization is the init method for initialization. And so the init method takes an empty box. And in here, this empty box gets a first and a last name. So that's what dunder init does. Why does it look weird? Well, because I didn't call it. It wasn't called here. I didn't explicitly write dunder init. But it's being called. And we can see that. And this is a tip that I give you when you're playing around with dunder methods. Just add prints to make sure that they're being called when you think they are. So if I rerun this, we like get any messages in the terminal. Now, this is maybe too low. So let me do the following. Let me go back to this. Over here. Close this one and maybe use this one in here. So what will I get on the screen? Will I get anything on the screen just by running my piece of code? Who thinks the screen will be empty? Okay, so everyone sees that I'm going inside init. Again, Python called it for me. That's the whole point of dunder methods. All right? Am I repeating myself too much? Am I making my points across? I think that's how you use English, right? Python is easy, English is hard. Okay. Very nice. So then let's see why would I want to use dunder stir and dunder wrapper in this class? So right now, what will I get if I run this? If I print John, what do I get? I get an object. I get trash. I get this thing right here. Yes, I get wrapper of this person. So what's happening here is I did not tell Python how to turn this class into a nice looking thing. And so Python falls back to some other thing. Let's leave it for another talk or the reason why it gets there. And it composes this because how weird would it be if you created classes, you tried to print them and you just broke Python? It would be weird, right? So Python makes sure that you can always print whatever you just created. But this is an ugly fallback. I don't even know if this is John or Ann or some other person. And so what you can do is you can implement dunder stir or dunder wrapper. Now, again, there's a distinction. If you can only implement one, because you only have 10 seconds or your life coding in front of an audience, you will implement dunder wrapper. Because it's the fallback. It's also the one that you want to implement for the developers. So for debugging purposes, this should be enough that you know exactly what object was being printed. And so in this case, what I would do is if I need to know exactly what's being printed, I'll make sure that I specify in some way the class that this belongs to, and whatever attributes I would need to reconstruct it. And so something standard is to, standard, at least for me, is to write something that you could copy and paste to recreate the object. Everyone comfortable with string formatting here, with the interpolation? So if I run this now, who can tell me what I'm going to get? I'm going to get exactly what's written here in line 11. I'm getting the, actually, I missed the quotes if I wanted to be able to copy and paste. But I get this. So Python was calling wrapper for me. But earlier I said that when you try to print, Python will call dunder stir, right? Well, the thing is, I don't have dunder stir here, and so Python fell back on dunder wrapper. But if I do implement dunder wrapper, dunder stir, sorry, it's confusing, if I do implement this and I say something just like, I don't know, self.first. And if you believe me, then you should know or you should be able to say that what's going to get printed now is just John. And indeed it is. However, and hence why this might be weird is that if I have a list with John inside, now because I'm inside a list, Python thinks it's best to just use the debugging quote, unquote representation. And so if I print my list, then I'm back to wrapper. So you don't need to memorize these rules right now. That's not the point. It's just so that you can see that different dunder methods, even if they look similar, they will always have different purposes. And they allow you to customize the way that Python interacts, sorry, yes, the way Python interacts with your objects. So all of the Python syntax, there's nothing magical going on. It's all syntactic sugar for specific dunder method calls. And so the more dunder methods you know, the more you can customize the interactions of your objects with the syntax of Python. So this is just a quick summary of what we saw. The dunder method in it is called when the instance is created and the dunder method stir is called when the object is converted to string. I hope this is fine enough for all of you. Now some common and useful dundas that I implement almost all of the time include dunder init, dunder stir, and dunder wrapper, and dunder EQ for testing equality of your objects. Because by default, the only two objects, the only two instances of your own classes that will compare as equal are the same instance. So if I create two John Smiths, they will compare as different. And I would need to say, well, if the person has the same first name, the same last name, and maybe the same social security number, then it must be the same person, right? And right now the thing that happens is it's only if it's literally the same object, it's going to compare as equal. It's the same thing as saying if you have a $1 bill and a $1 bill, and if you compare them, Python is going to say it's false because the serial numbers are different. But you can go to a supermarket and either bill is fine, right? They're the same value. And so you would want to use EQ to say, if the value is the same, then they're the same bill for comparison purposes. So now let's take a look at another example, and that's how sequences interact with dunder methods. Sequences rely on a dunder method that's called getItem, which is used for gettingItems, and a method called setItem, which is used for settingItems. So I think you're all above the requirements for this talk, and some others, but these two are like the main ones if you want to think of accessingItems and settingItems. And so for example, an exercise for you, not for right now, because I'm almost done, but for you to take away home is, what if you wanted to implement a none list that always returns none, whatever index you access, it always returns none. Then you'll want to implement getItem and make sure you return none. And if you want to, sorry, I'm getting ahead of myself, so that's when you access items, you call getItem. And if you want to do assignment, then you will use setItem. That's the point. And let's see this in action. So this is the challenge. Actually, no. So I'll leave, I have another one which is nicer, but also slightly harder. So I'll do the simpler one with you, and then you'll have the slightly harder one as a challenge for you for later. So let's implement this real quick. How does this go? Let's call this the none list.py. So you're going to create a class, right? And to initialize, let me just do, I don't care about initial arguments, because in here I don't have any. I just want to save a place. I just want to have some place where I can save the values that people already gave me. So let's do something like self.values equals an empty dictionary. Now, the main behavior here is when I get an item, what does Python need to tell me in order for me to produce the correct item? I need the index. So this is the argument that Python will give me automatically. So when I type something like this, my list, square bracket, zero, square bracket, then the zero will be the index argument here. And so what I need to do is, I just check if the, actually I can do this, return self.values.getidx. So if values is a dictionary, .get will try to fetch the value associated with the key. And if there's none, it's going to return none. And so running this should already give me part of the behavior. What was the name of the file? None list. Did I do something wrong? No, I heard someone say something. So none list, zero. Is it going to break? No, it print, it gives me none, but I didn't print it so we can see it. Everyone comfortable with this? Because I want to be able to return values that get set later. The spec wasn't clear. My point is, after I assigned something, I want to be able to get that value back. And so when I set an item, what do I need? I need the key, it was being set to end the value itself. So let's call it actually key value. So key here, key value, and then what do I do? Well, self.values, key equals value. And now, if I re-run this, I get a none list, I set some value on it, and I should be able to get it back, hopefully. Okay, so it seems like it's working. And it should still print something for an unknown key. It should print none. So if you ever want to create your own sequence, then you'll definitely need these two. Or if you want to read the only sequence, then you can leave these out. Or if you want a write only sequence, then you would leave get item out. And that's the whole point. These are methods. You implement them, and Python does things with them. Now, the challenge for you is then to write a, so the recursive Fibonacci, that's, we all know that, so let's do something nicer. You're going to create like a generic recurrence relation. You use set item to set the base cases. And then your get item should use the base cases to figure out whatever items you ask for. So that's the challenge for you. Come find me if you want me to be clearer about the spec or if you have a possible solution. And there's dundas for everything, like I said. If you want to do membership testing with your objects, there's a dunder for that. There's a dunder, two of them, for context managers. There's dundas to customize attribute access. There's dundas for everything. So if it's in the Python syntax, there's dunder methods for that. So the two main references for this talk are an article I wrote about it, and obviously the Python documentation. It has a very comprehensive page about the data model, which is, which respects all of these dunder methods. There's also a bunch of chapters on a free book that I wrote about this, these dunder methods, string and wrapper, bull, other stuff. And everyone always asks what I made my slides with. So it's these things, snapify.com. Yes, it's awesome. No, I have no affiliation with them. And I don't think we have much time for questions. I'll be around. Feel free to join me in my poster session right now and for the sprints in the weekend. Thank you.