 Hi. I'm Anjana Vacchial. Hello. Hope everybody's enjoying the week so far. I don't know about you guys, but I've seen a lot of slides this week, so today I figured I'd do something a little bit more experimental. No slides. I'm just going to show you some code. Some really, really silly little code examples. Please do not take them seriously. Don't write code, like what I'm going to show you today. But my hope is that they'll illustrate some of the fun special dunder or double underscore methods here, which are also called special methods, magic methods, but I like the term dunder the best, so I'm going to call them dunders. As I mentioned, I put up all these examples in a little repo, vacchiala slash dunders. What I'm hoping is that at the end of the talk, to have a couple extra minutes to discuss with you guys, I'd like to do this more interactively. If people have other dunder tips and tricks that I am not able to cover, because there's too many wonderful things to fit into 20 minutes, I would love to have people discuss them afterwards, and possibly even after the talk if you can contribute to this repo, add stuff to the wiki, open issues, start discussions, and if you have fun little code samples that you want to add, there's a directory in here, sharing is caring, where you can put in whatever you want and file PR and I'll put it in there. So that's what I'd like to do today, because dunder methods are super fun, if you ask me. So what are the dunders? Well, they're these special methods and attributes surrounded by double underscores, which is why we call them dunders, and some of them are our best friends, right? So everybody probably uses dunder in it all the time, it's our basic constructor method, so here I've got a custom class, I'm going to call it a stringy int, and it's going to be a weird kind of number. I'm constructing it with this dunder in it, I'm giving it an attribute called value, and then another dunder everybody probably is already super comfortable with is dunder str, maybe there's a better way to pronounce that, I don't know, where we can have whatever kind of string representation we want. Usually it's just like the value of the object, but this one's going to be more exciting, it's going to have this is why you should never live code in presentations. So if we run this little module, we get to use another dunder that's super fun, which is this beloved if dunder name equals dunder main block, which as you all probably know is run only when you run the Python module itself and not when you import it into something else, so that's cool. Let's run a little interpreter, and I've set up some stringy ints here, so we've got one is a stringy int object, and two and three. Awesome, and if I print them since I've got my dunder str, I have fun string representations. And so another dunder that we all probably know and love is what is giving me this weird Python object-y looking string representation here, and that's dunder wrapper, which if I uncomment this here is supposed to be another type of string representation that's more the code object itself, it's supposed to be more unique to the object. In this case, I'm just going to have it be more boring and just print the string of the value, and so we can see that when we quit this and do it again now, now when it evaluates one, it prints out just the values, so I don't have to look at all this gobbledygook addresses and whatever. Okay, so far so boring. We all probably know and love these dunders. What about other fun stuff? So when I have regular integers, I can add them or multiply them. That's because the built-in int object has these fun operator dunders, like dunder add and dunder mole, which are used by the plus and the star asterisk operators to perform these mathematical operations, and I can overload them in my own classes by just implementing these methods. Super cool. So let's see here. What happens now when I have my one and my two, and usually if I add two integers together, I get, you know, reasonable things, like three, but if I add my stringy ints together, ah, it's giving me an integer that's just mushing them together like strings. This is a completely useless class. You probably don't ever need to implement anything like this, but the point is, you can't because Python magic. So what if I want to add a stringy int to a regular int? It doesn't work. The operand type is not supported. That's because it's looking for a dunder add function on the integer to the left of the plus operator that works with a stringy int object, and it doesn't find one. So if it tries to find the dunder add method on the left-hand side object but doesn't find something that works for the right-hand side object, Python will also try looking for these special R methods, which are like the sort of opposite implemented for the right-hand side object, like, for example, dunder add, which is possibly the best-named method in the world. So this one will be called on the right-hand side object as sort of a fallback if it can't use the regular dunder add on the left-hand side object. So now we can see that if we try one plus one, aha, it works now. We didn't change anything about the built-in int, but we use this dunder add, and it is super rad. I'm sorry, I can't help myself, you guys. Okay, one other operator that is a little bit different is this equal equals, right? We all know and love it. One is one, and the stringy int one is itself, but it would be cool if we knew that those two things were similar somehow. So I can do that by implementing dunder eek, and there's equivalence for all the, you know, less than, greater than, etc., etc. In this case, I'm going to try to make it work for anything that I can intify. So again, don't write code like this, it's just an example. So now if I try, okay, one is still itself, and my stringy one is still itself, and now, hopefully, oops, wrong direction, aha, now it knows how to compare these two different types. So the dunder eek function also is a bit special because it's also used for, for example, making objects hashable so that it can be used like as keys in a dictionary. So if I have a dictionary d, right now, if I want to make an integer a key, that's no problem, we do that all the time, but if I want to try using one of my stringy ints, it says, oh no, it's unhashable. Well, we can fix that, of course, with a dunder called dunder hash. So if I implement this dunder hash function, what I want to do is return a unique hash value for whatever this object is. In this case, I'm going to do kind of a really silly one, which is just returning the integer value itself. Ideally, you'd have something better than this, but the important thing is that you don't want to implement this kind of thing on an object that shouldn't be hashable, like a mutable object, you don't want that to be a key in a dictionary, but these are not mutable, so I can use it. So let's see now, if I have my dictionary d, okay, I can still use my regular integer keys, let's try now. Oh, I overwrote it, sorry. Okay, so now I've got, it's not complaining about the hash ability, and because of my dunderrepper function, it's difficult to see this, but if I do for key and d keys, it really is harder to type when you're up here, right? Let's print the type of each key. Okay, I see I've got one int and one stringy int, so it's working. So we can now use this new custom type as a hashable type for dicks. All right, one other thing I wanted to talk to you about with these stringy ints is a fun little dunder that's an attribute, actually, not a method, which is dunder slots. So dunder slots is a bit different. When I have an object like a custom object, usually I have a dictionary dunder dict that stores all of the attributes for that object. So that's what allows me to do like one dot value and get something out. And if I assign something new to the object, if I look at the dict, I see, aha, it got added to this dictionary. It's just a regular dictionary, you can mess with it however you want. But the thing is that dictionaries take up space, and so whenever you create a new object, Python gives you this dunder dict for all of the object's attributes. And it might be the case that for an object like stringy ints, you know that it's never going to have more attributes than value, it's only going to have that one, or maybe you just have a small set of attributes. And if I'm going to be creating like millions and millions of stringy ints, creating all those dunder dict dictionaries could take up more space than I want to use, it takes up also a little bit of time. So what I can do is use this, declare this dunder slots and name out all of the attributes that I want on my object. In this case it's just value. And what that does is prevent the dictionary from, the dunder dict dictionary from being created. So if this is right here, if I try now accessing the dict directly, ah, it doesn't have one. Does it still have its value? It does indeed. Can I add a new attribute? Nope, I can't add extra attributes. So it's basically constraining the size and shape of this object in a way that if you're creating gajillions of objects, that efficiency might actually come in handy. So I thought dunder slots was pretty cool when I heard about it earlier this year. All right. So let's see. Time flies when you're having fun with dunders. There are a lot of other fun dunders that we can use to make like container objects, for example. We already saw how we can make objects that are kind of simpler, you know, like numbers, numeric types. But what if we want to make things that have contents? So, for example, let's say I want to make a list, but I find lists really boring because, you know, when you add things to them, you append things, you know they're going to show up at the end. And when you index things, you know that you're going to find the right object for the right index. What if we want a crazy list where there's just an element of randomness? So in this case I've made a silly little object called a crazy list, which I've got my dunder init, I've got my dunder repper, I'm adding a little append method, just because all good lists need one, but instead of, you know, appending things to the end of this self.values list, I'm just going to insert them at a random place. Because why not? And so, again, I'm using my dunder name thing in here to run some code, right, when I run the module. Let's see here. All right. So I've got an L object, and it's got some elements in it. I want to find out how long it is. Oh, no, it has no length. That's not good. What should I do? Probably use a dunder. In this case, the one I want is dunder len. This is what's called by the built-in len method. So a lot of the built-in methods that Python, that we were used to using in our super beautiful Python code, they depend on these dunders, and one really simple one is dunder len. So in this case, instead of telling you actually how long the list is, I'm just going to sort of give you a random number that's somewhere in the vicinity of its length. Super useful, right? I hope you guys noticed the word abusing in the title of this talk. So now, if I try to len my object, aha, it gives me a completely wrong number, but at least it gives me a number. Sweet. Okay. So there's another, let's see. I'm going to try and skip ahead here. What if I want to do four item in my list, you know, print the item? Oh, no, it's not iterable. That's not good. How do we fix it? We fix it with a dunder. Yay. In this case, dunder iter. I skip dunder bool here. That's used for if l. You guys can get the idea. But dunder iter is probably more important if you're trying to make some kind of sequence object or something that you should be able to use in for loops and that sort of thing. You're going to want to implement this. And how it works exactly is a little bit more complicated than some of the other dunders, but in this case, I'll just point you to the documentation because we're running out of time. And in this case, I'm just going to kind of yield a random element from the list in the completely wrong range of the number of elements that may or may not be in my list. So really useful dunder iter function here, but hopefully you get the idea. So now let's try to do our four i in l print i. Okay. So I have a little thing in here that shows when dunder iter is getting called. It got called for that for loop. And it's doing something really useless, which is not only is it printing the wrong number of items, but it's also just printing question marks sometimes because mystery. But the point is that we can use this now in for loops. So if I wanted to define a really super useful dunder string method that uses a for loop to print out all the things in the list, I could. Okay. But what about, so usually in a list, like l, I want to be able to get a certain element by using this bracket notation, but it doesn't support it. If only it did. If only we had implemented dunder get item, which lets us pass an index or a key to these brackets for our object. And so depending on whether you want your object to be indexable using integer indices or you want it to be like a keyed item, like a dictionary, you can define this method to let's say look for a certain type, like only integers or to just handle anything, which is what I'm doing here, is I'm not even caring about what the key is or the index is. I'm just going to accept it and just give you a random item from the list no matter what you ask me for. So now I have my l. If I want a certain index, yeah, sure, whatever. Yeah. It's totally working. Great. And what if I want to try to access it as if it were a dictionary? Sure. No problem. Yeah. We'll just give you random things from the list. The point is, if you were to make an actually useful dunder get item method, then boom, you've got a dictionary or an indexable sequence or whatever you want. Okay. And there's also a dunder set item for the equivalent, which you can imagine for setting a certain item at a certain index or setting the value of a key. Okay. One other dunder that I want to talk about with containers is dunder contains. So if you have an object where you're going to be wanting to test for membership, like if x is in l, if you don't have dunder contains implemented, what you'll see is if I do, let's say, I want to see if 2 is in l. If you don't have dunder contains implemented, it's actually going to use the dunder iter and go through everything in the list and see if it finds something in there that's the thing you're looking for. That can be a bit slow, depending on various features, like, for example, how far close to the beginning of the list, the item you're looking for is. So if you implement dunder contains, this can be, doesn't need to be, but can be a faster way of testing for that membership. So depending on what your use case is, if you need something that you can really quickly decide whether something is a member of, like a set, let's say, dunder contains can be a good idea. So now we see that if I ask, okay, is 3 in l? See how my dunder iter didn't get called? I had that little print statement there. It's using dunder contains first. Okay, so a crazy list that's completely useless, but nevertheless showcases the magical container dunders. One last thing I want to show you, and this is probably my favorite, are some fun function dunders. So I have a little function here called add. It's super boring. It just adds two things, spam and eggs, whatever. But because Python is magical, since I have this doc string in here, I can, if I ask Python to help me out with add, it tells me okay, cool, the contents of that doc string and information about the function itself. Sweet. And when I add two things, it, you know, it does what the function says to do. But what if that's not cool enough for me and I want to hack my little function on the fly? So this is probably something you should never, ever do. But if you were a cat, you wouldn't care about adding two numbers together, you would want your human to give you more tuna. So what I've got here is a little function which makes use of some fun function object dunders, dunder doc, which contains this doc string. I'm going to change it to something more cat specific. And dunder code, which is actually the code object, the content, the functionality of your function. You can actually mess with that. You can replace it to be, for example, the contents of another function called more tuna, which instructs the human to give more tuna. So now, if I, if I have my regular add function, okay, add still good, add, it still works. Great. If I now catify it and I try to add two things together, it actually changed the functionality that's attached to this add name. And similarly, the doc string is different and useless. But this is something that you probably don't really ever want to do. But the important thing is that you should know that you can. And so if you ever see this kind of messing with going on, be really careful. All right, last thing, hopefully I have time for one more dunder. The, the with keyword works with two special dunders called dunder enter and dunder exit. And this is what allows us to define a context manager. So basically what happens is this dunder enter method is called whenever we enter a with block. And it can set something up for us. Like for example, if you use like with open file, right, it'll do some things like read in the file object. And then when you exit the with block, it calls this dunder exit method, which can do something useful like closing the file object. In this case, we're going to have it do something less useful, which is it's going to catify a function on the enter, the dunder enter. And it's going to uncatify the function by replacing the original dunder code and dunder doc with the boring human code and the boring human doc on the dunder exit. So what we should see here is that if I have my regular add function, it works. But if I do with cats in charge of add, and then I try to add two and three, and let's do something else too. Let's, let's just call the help and let's add, I don't know, four and five and six, sure. So what I'm doing on the dunder enter is catifying this add function. So we should see that any add calls I make in the middle of this block are the catified version. But when I exit it, I'm setting it back so any add calls I make after this block should be normal again. And just because maybe you guys want something that's actually a tiny bit useful out of this doc, I'm also putting in a timer here. So I'm timing when the cat reign begins when I go into the dunder enter method. And then I'm logging the time of when the cat reign ends when I leave in the dunder exit method and I'm printing out how long the cats ruled for. So this is an example of something you might actually want to do is write your own timer, for example, to time an arbitrary block of code. Okay, so let's try it out. Okay, so it called help and it's meowed and we saw that it called add twice and I'm actually using the numbers as the arguments to determine the Rs on per here. And then it told me how long it took, how long the cats were in charge. So just a little example of some things that you can do with these context managers, depending on what you're trying to do with your code, it can be a really useful pair of dunders. All right, so that's all I wanted to tell you about the dunders that I find cool. I would love to hear now if people have other ideas for dunders you find cool. And just before we kind of open it up to everybody, I just wanted to point out that the documentation for the Python data model has information about all kinds of dunders that you could possibly want to know. So if you're curious about any of these, yeah, check that out. All right, what do you guys think? Dunders to share? Thank you for your talk. If you overwrite dunder all in a module, then you overwrite what can be imported. So you can just say all is just these three functions. Okay, dunder all is not in this thing, but that sounds really awesome. So if you underwrite dunder all, you can overwrite what's been imported in the module, what can be imported from the module, that's cool. So is that something I should be able to access right here? Like, is it a... Try it. No, okay. Is it something that would be in the vars? Like in the... I've only ever written it. Okay, no. Where would it be? Anybody? If you put it in your module. Just anywhere. Should be a list. Okay, so if I want to say that only... We can only import catify. Like, is it... Would I do it like that or would I use the object itself? Okay. Does the order matter? Does catify need to come first? Okay, sweet. Let's see. So if I... No, wait, sorry. This is what would happen if I import from that. Okay, so let's try import from catification. Import add shouldn't work, right? Oh, okay. All right, all right, all right. So if I do from catification import star, then add should be aha. But catify is a thing. Cool. Thanks. Anybody else want to share stuff? Yeah, and it'd be cool if whoever just mentioned that could put a note in the wiki or add a little example. That'd be awesome. Sweet. Anybody else want to share something? Well, if you're looking for dundas that you can do crazy things with... Aren't we all? Yeah. The one to look for is dunder new. Dunder new? Yes. Which if you want to do crazy things, normally you would return your object instance, but you could return whatever you want. So for instance, if you return the integer 42, instantiating your class will get you the integer 42. Sorry, if I run it... If you're in your dunder new in a class, if you put return 42 at the end, instantiating that class will get you the integer 42 and not an instance of that class. Okay. So if I, in my stringy int, if instead of an integer I want, whatever integer I wanted, I just want always the answer to life, the universe and everything, I could do dunder new, self, whatever... Does it call with the value? Not self. You get class. CLS. Yes. And you get the value also. Okay. So it'd be like this? Yes. And I could just return 42? Exactly. And so now, if I run this, I should have, I had like one, should be 42. Exactly. Awesome. Very cool. Thank you. And if you do type on one, it'll say it's an int. So tricky. Thank you. Yeah, if you could put that in the repo, that'd be awesome. Anybody else? Yeah. So something I actually find really useful is if you still live in Python 2 and you don't have the LRU cache, yeah, you live in Python 2. Sorry about that. You can use the underscore, sorry, the dunder missing. You use the dunder missing. You get the cache out of a dictionary in five lines of code. Sorry. Could you say that again? Maybe more slowly? Yeah. So if you inherit from dictionary and you implement dunder missing, whenever an item is not found, you can specify a function. Okay. And that's a cool way to implement the cache. Awesome. So like, I imagine that would also be useful for things if you want like default values, like a default dict or something with that work. Very cool. Dunder missing. Yeah, if you could add that too, that'd be awesome. You can implement method call and dunder call to call object as function. Right. Okay. So if I have like an integer, shouldn't usually be callable? You need to method new. Right. Let's replace it. Okay. And it takes, what, self, right? And any arguments? Can we like just take however many we want? Will that work? And I don't know. I'm just going to print like yay dunders and return, I don't know, 42. Okay. So now if I have one, okay, it's my number and it's still hopefully a stringent. Cool. But I should be able to call it. Yay dunders. Sweet. Dunder call. And you can implement method and dunder get, dunder set, dunder deal, and dunder delete. Right. Right. So yeah, we saw like some of the getting and setting, but there's a bunch of other dunders like delete and whatever, which you can do, special cleanup code or whatnot that you need. Very cool stuff. Thank you. I don't know how we are for time. Are we? So actually the launch break started. All right. Well, thanks everybody for sharing. And yeah, if anybody wants to contribute to my little dunders repo, I'm hoping that it can be like a conversation starter. So go for it. Thank you.