 My name is Lindsay Brackman. I am a Python developer up in Chicago and today I'm going to be talking to you about multi-methods and how to implement them in Python. So right off the bat, I want to be honest, I haven't given a talk in a few years now. I think the last time was actually at PyOhio, so it's good to be back, hey everybody. So I think part of the reason for me not giving a talk in a while is I kind of always knew that this was going to be the next thing that I wanted to talk about. It just always kind of interests me, but the sort of, you know, question that would like keep me up at night for hours was our multi-methods, even really Pythonic. And that's kind of a deal-breaker question when you want to give a talk about Python at a Python conference, like I don't want to tell you like, hey look at this really cool thing, never use it, it's terrible. So if you're like me and you're a believer in the Zen of Python, and I mean hopefully we're all believers in the Zen of Python, if anyone here hates it, like I guess that's fine or whatever, but like you should be. Should I hear someone poo? Then you know that there should be one and preferably only one obvious way to do something. So our multi-methods obvious and I kind of wrestled with this for a bit until I really started to dive into the code that I think multi-methods can help us improve. So in that way I think this implementation can be an obvious and Pythonic solution to a problem. Also back in 2005, a little someone named Guido Van Rossum, creator of the language, wrote a blog post about how to implement multi-methods in Python. So with his blessing, I think I'm just going to go on with my talk. So here's a breakdown of what we're going to be talking about today. First I'll go over what multi-methods are, what they're not. Also talk about the anti-patterns that I think multi-methods can help us avoid in our code. I'm also going to say multi-methods about 3,000 more times, so I'm sure I'm going to trip that up a little bit more. And then we will look at some actual concrete examples of multi-method implementation. So what are multi-methods? Some of these terms you may have heard in the past, I would hope within the last five minutes you've heard multi-methods before. If you haven't, like, hi, welcome to my talk. So some of these, at least in my experience, I've seen used interchangeably. Sometimes that's correct, sometimes it's not always technically correct. So I'll kind of explain that as we go through these definitions. So let's start out with multi-methods. They are a feature in some programming languages in which a function or method can be dynamically dispatched based on the run-time type of more than one of its arguments. So just a quick example of that, let's say we have an add method that takes in two integers, adds them together, and returns the result. Let's say we also have another add method that's responsible for taking in two strings, concatenating them, and returning the result. So in our code, if we are calling that add method on two integers at runtime, we'll be able to determine the type of those parameters are integers and the call is going to be dispatched to the first add method. Now, if our code calls the add method on two strings at runtime, same thing's gonna happen, we'll determine the type of those parameters and dispatch the call to the second method. So the key word there is multiple dispatch, or I'm sorry, the key word there is dispatch because if you haven't guessed it yet, multiple dispatch and multi-methods are sort of describing the same thing. Now if you come from maybe more of like a Java background, this might sound a little bit similar to overloading. So this is the term where sometimes you'll see people use dynamic overloading or just use overloading when they mean multiple dispatch. So let's take a peek at overloading. So overloading is the ability to create multiple methods of the same name with different implementations, so that sounds familiar. The overloaded function must differ either by arity, so that's to say the number of parameters or the data types of those parameters. Method overloading is usually associated with statically typed programming languages that enforce type checking in function calls. And the determination of which of the methods are used is resolved at compile time. So that's really the big difference between overloading and the multiple dispatch that we're going to be looking at in Python. Python does not have the concept of compile time, static type checking, all of that type checking is going to happen at runtime. So for the sake of consistency in this talk, I'm going to reserve overloading for this case when we're talking about statically typed programming languages like Java. And multi methods and multiple dispatch is what I'm going to use to describe the implementations that we will learn about going forward. Also on the list, single dispatch, my little emoji isn't quite right, but that's okay. So single dispatch is very similar to multiple dispatch. The only real difference being that the method calls are dispatched based on only a single argument and not multiple arguments. There was a decorator added to Python 3.4 with PEP 443 that adds a single dispatch, it's a single dispatch decorator that was added to the Functools library. So without really going into this in super detailed, mostly because I'm not even completely comfortable with the subject. All methods, all class methods in Python are virtual. So Python already has the concept of dynamic dispatch and that is to say single dispatch. The decorator that was added in 3.4 gives us the ability to create multiple methods of the same name within the same scope. So that implementation is going to look similar to the multiple dispatch that we'll be implementing in a short while. But yeah, this is mostly just here in case people are like, wait, I thought Python already did this sort of thing. It kind of does, but I think multiple dispatch is cooler, so we're just going to talk about that. So to summarize, multi methods and multiple dispatch are describing the same thing. Single dispatch is quote unquote a subset of multiple dispatch. Python does support single dispatch. And overloading is just that Java thing that we're not going to talk about anymore. Thank you. All right, let's get into anti-patterns. When I was practicing this talk with someone that's sitting in the audience right now, he told me that my slides needed more pictures. So here. So multi methods I think are interesting in their own right. But what makes them, at least for me, hopefully for you, a little bit more interesting is having an actual concrete problem set that they can help us solve. So I want to take you through a couple anti-patterns that you may have seen in your own code. I know I've written these before, and now, I guess after this talk, I have to go home and rip some stuff out and rewrite it so as not to be a hypocrite. But let's dive into these. The first is the giant if-lfl block of is-instance checks. So the class that I have up here is a weekly typed outer class. I'll explain my thought process behind this. Python's a strongly typed language. So if you try to add a string and an integer together, you'll get an exception. Because Python doesn't do a lot of that cool implicit, sometimes terrifying typecasting that a weekly type language like JavaScript might do. And does anyone ever open the dev console in their browser and just add types together in JavaScript just to see what happens? OK, I'm not the only one that just does that for fun. Sometimes you switch the order of the types and then different things happen. And it's like, whoa! So if you're like, oh my god, I wish Python could do that, too, you might use the weekly typed outer class. So in this class, we have a single static add method that takes in two parameters of unknown type. As we move through the code, it's going to check the types of those parameters before deciding what to do with them. So in the first case, we're checking to see if both are integers. Then we'll return the results after adding the integers together. Next, we'll check to see if there's strings. Can catnate the strings, return the result, et cetera, et cetera. So this isn't too bad right here, but it has the capacity to get much hairier. And that's kind of what scares me and makes me hate this code a lot. As you want to add support for more and more object types, you're going to have to tack on more and more LF, LF, LF statements. And as you start nesting, if else is, underneath outer if else is, you're going to find yourself with this giant mass of unit tests that are all effectively testing one function, but all the 50,000 logic branches that you have in that function. So that's, oh no, no, come back to life. OK, yeah, anti-patterns. So this also isn't the most object-oriented approach to doing this. So yeah, this isn't great. There is an alternative, though. So let's take a look at the Overlea verbose method name solution, where we have the same weekly typed pattern class. Each one of those if checks, each one of the is instance checks has been pulled out into its own much more granular, much more easily testable method. So we have add integers, add strings, and then we have add integer and string. And then I think you'd see where this is going. This is going to become really gross. I don't like writing sentences in my method names. Verbose method names are great in unit tests, because you don't actually have to call them anywhere in your code. They should be very descriptive. But when you're just using this class in your own code, I think calling weekly typed adder dot add in a string is just a little, no, I don't like that. Also, like I said earlier, Python doesn't really have the capacity to do any of this cool compile time static type checking that Java might do. So there's really nothing protecting you from calling add strings without string parameters. Nothing really enforces that in Python. So some might argue, well, we can get around this by combining these two kind of gross things into one giant gross thing. So here we have the weekly typed adder class. We have our nice add method that's going to be handling all of the is instance checks for us before directing the call to these verbosely named private methods, quote unquote private methods. And why? Why combine these two gross things together to get a bigger gross thing when we can try something different? Personally, I think it would be super nice if this class could just define a bunch of add methods. They're all just called add, super easy to call. And you just throw types at it, and it knows what to do with those types. But that doesn't work in Python. Like, you can't just do that right off the bat. So to kind of understand why, I like putting PDBs just in code when I import it to kind of step through and pretend I'm the interpreter a little bit. So let's kind of pretend we're going to do that. So here we have this class again. And we're actually going to be calling the add method at the end of this code. So if we were the interpreter, and this module was being imported, we would start at the first line, see that we have a class definition, and then we're going to step in to this first function definition. So in Python, namespaces are primarily implemented as Python dictionaries. So each class definition is going to give that class its own personal little namespace. So you've probably seen that if you've used the vars method or checked out the dict attribute on classes. So as we're stepping through line by line, the first add method that we're going to be adding to the namespace is this one at the top. So we'll be mapping the name add to the static method object at that address and memory. Then we're going to step on down to the next method definition. We're going to do the same thing. Register the name add to the second static method object at that address and memory. And finally, we are going to register the third add method, or sorry, the name add to the third static method. Oh, sorry. Did someone say it? Yep. As written, this code will not. Correct. The class definition, or the way we define this class right now won't throw any errors. It's the call at the bottom where we actually try to run the code that will throw errors. But if you were simply importing the class, you would be totally fine. If you were running this directly in the interpreter, that's where you would see a problem. And I'll explain that in a bit. But yes, good question. Nothing you probably wouldn't know that anything weird is going on here if you didn't have the knowledge of how Python really handled these namespaces or how it handles, or in this case does not handle multiple dispatch. So you'd probably just think, OK, my code's doing whatever. So we have all three of these methods. The interpreter has hit each one as we're stepping through. So what you may see going on here, if you're familiar with how dictionary implementations work, as you update a dictionary with a key that already exists in the dictionary, the value of that key is going to be overridden. So when we actually call add down here, we're not, oh, sorry, I should probably explain. Here I'm actually trying to call it on two integer objects, which would hopefully be handled by the first add method in the list. But what's happening is it's using the static method object at the address and memory of the final one that was defined. So this is where we're actually going to see an error, because the third method that we've defined here tries to cast the first operator to a string and then do a string incantation. So that's not going to work when we cast one to a string and then try to add it to the integer, too. Sorry, I feel like I'm hearing voices up here, but it might just be like everyone next door. So yeah, basically at this point, the interpreter has completely forgotten about the first two methods that we tried to define, and it only remembers the last one that we defined, which isn't really helpful for our weekly typed adder class. So I guess, yeah, you can't do multiple dispatch and Python, my talk is over. Does anyone have any questions? Just kidding. So I mentioned that blog post earlier. I have a lot of warm, fuzzy nostalgia for this blog post, because at my last job, we actually used it as inspiration to implement this really cool thing. And it was one of those projects where you feel like you dive completely into it and learn more about the language in a day than you ever had before. And it's kind of like, whoa, and mind blowing. And then the code never hits production, and it's sitting covered in dust and a repository somewhere, and that's really sad. But before that happened, I was really excited about this blog post, so I would love to share it with you. So I'm going to quickly walk you through five-minute multi methods in Python by Guido van Rossum. And just keep in mind, this was written in 2005, and I think, oh, is that Python 2.4? So yeah, just for context, I'm going to pull just some of the relevant code samples out and try to go through this quickly, so I can actually show you a live implementation of it. So the first code sample that we have is basically Guido reiterating that the if-lif-else blocks of his instance checks is gross and tedious, not very object-oriented. And then he goes on to say that multi methods probably aren't very object-oriented either, but I'm just not going to get into that right now. So if we wanted to implement multi methods in Python, this might be a good first pass. So let's say this is our MM module. This module is going to define three things. First is a global registry. It's just going to be a Python dictionary, empty at first. That registry is going to be responsible for mapping function names to multi method objects and to understand what a multi method object is, we can look at the multi method class. So the multi method class will be responsible for holding onto a type map, which is another Python dictionary. And that dictionary is going to be responsible for mapping the types that a function is responsible for handling to that actual function object. There is also a call method that will allow us to make instances of this object callable. So the call method is basically going to take the argument types that we're trying to call the instance on. It's going to grab the, sorry, I feel like I'm saying instance too much, and it's starting to sound weird in my head. We're basically going to build a tuple of the types of the arguments that are being passed into this multi method object, and then we're going to check to see if those types are a key in our type map dictionary. If they are, we're going to find the function that key is mapped to and return the result. If it's not, we'll raise a type error. Finally, we have a register method, and that's going to be responsible for actually registering the types that the functions are meant to handle to those function objects. If we try to register two functions that are both of the same name and are both responsible for handling the same parameter types, we'll raise a duplicate registration type error. And last thing in this module, sorry, if I'm going through this kind of fast, hopefully, like actually seeing the code running will clear things up a bit if you're a little lost. But the last thing that we have in this module is the multi method decorator. This is what we're going to use to decorator functions and actually turn them into multi methods. So this decorator is basically just going to grab the function name, check to see if that name already exists in our global registry. If it doesn't, it's going to create a new multi method object for us. If it does, it'll just pull out the one that already exists in the registry. And then it's going to call the register method on that object and register those types to that function that we have decorated. Then it will return the multi method object. So at this point, we're replacing our non-uniquely named functions with a single multi method object. So here you can, at the bottom, if you can see, they're sort of just the def-foo example that's not super helpful of functions that are decorated with the multi method decorator. So the first one will be responsible for taking in integers, sorry, two integers. And the second one is responsible for only taking in a single integer. So I think a drawback to this, oh yes. Yep, right now. So a problem with this implementation is that it does not support a function being decorated multiple times. I sort of like kind of threw together like what that might look like, even though this isn't technically correct, but hopefully it illustrates the point. Basically what's happening here, the decorator closest to the function is going to be executed first. So let's go back to our, the actual decorator code. So the function is going to be passed to the decorator. We're gonna grab the name and handle the registry. Yeah, yeah, yeah, very cool. So by the time this is executed the second time for the outermost decorator, the function that we're being passed in is no longer that original function. It's the new multi method object. So that's where we're gonna hit some trouble trying to access that name to get our registry all sorted out because we've, at this point, lost that metadata about the function. So to get around this, it's okay. Saved the day. Well, Guido did, he wrote all of this. So we can add two lines to this decorator. Basically we're going to monkey patch a last reg attribute to the multi method object the first time it's called and that's going to store the original function. So then if we have this decorated multiple times then the decorator code is executed multiple times for the same function. We can just go in and access that same attribute and get the original function and then we have the function name. So, oh, that's not the end. Let's actually take a look at this code and give me a second while I blow this up. Oops. Do, do, do, no. All right, no. You might have to not do this multiple tab life. So here we have, is this big enough can everyone in the back see this? All right, I will try to scroll as much as I can to respect how far down the screen y'all in the back can see. So here we have the MMM module. This is pretty much exactly the way Guido implemented it. I made a few syntax changes just to make it look a little more Python three but I tried to stay true to the original implementation because nostalgia. So we have the global registry up top, then our multi-method class that's doing all the things that I said it did earlier. We have the call method, so the instances are callable, the register method, and then finally our multi-method decorator. And here I have implemented it with the last reg attribute. So let's take a look at this weekly typed adder again. I have updated this a little bit in respect for being dry. I've combined the simple just plus operation here. So strings, two string parameters and two integer parameters can be handled by the same method. And then I've also added the string and int in string case because again we're building a tuple from these arguments so order is going to matter when we're checking the global registry, sorry, not the global registry, the type map for those specific types. And also to try to prove that we can do this in a super object oriented way, I extended the original weekly typed adder class and added this, okay, I don't know why you would do this but I was just really needing an example. So here we can add a dict and a string together. And what that's gonna do is just update the dictionary with the string as a key. So maybe JavaScript does it, I don't know. And then we have a method down here that's like totally commented out and I'm definitely not gonna get to that later. So let me pull this tab back over here. Let's fire up the interpreter and just see if we can import this. I'm like trying to type while also looking at the clock so this is just gonna be a recipe for disaster. All right, everything imported. If there were problems with this code we would have gotten one of those type error exceptions right off the get go. So the fact that everything imported that's good, let's try it out. So we have our weekly typed adder. Let's see if we can add integers together, cool. Let's add strings together, all right. Oh, and I guess we have a string and an integer. Okay, so that's all working. Then, okay, yeah, then we have our extended weekly typed adder. So we can do this again. I don't know why you would want to, but hey, it's my code demo. So all right, that's working. So that's super cool. Now if this were truly like object oriented inheritance the way that we're used to seeing it I wouldn't expect the parent class to have access to the child's method of adding a dictionary and a string together. Well, that's great. That's one of the problems with this implementation when you try to jam these into classes. So let's also take a look at this mysterious commented out code. So what if we wanted our extended weekly typed adder class to override the implementation of string addition in the parent class. So in this case, maybe we would want to try to cast the operators to integers before adding them together. And you know, if we can, we would just do the regular string concatenation. So let's save that and pop back over here and close this out and try our import again. And now we see this duplicate registration type error. So what's happening here is the type map, or I'm sorry, the global registry for, the global registry that exists in the MM module is already mapping the name add to a multi-method object. That multi-method object has a type map that already contains the tuple of strings mapped to the original add method that we defined in the parent class. So when we try to reassign this add method, or I'm sorry, like re-implement this add method in the child class, it's going to raise an exception. So that's not great. What can we do about that? Nope. No good. There we go. So I tried to take a stab at, first off, just improving this implementation to see if we could actually make it work. Because I guess that's what you do when you encounter a bug. You just sort of slam your head against it and keep throwing more code at it until it works. So before in the original MM method, what did we have? We were simply grabbing the function name and then adding that to the registry. So what if we tried to use a fully qualified name where instead of just taking the function name that exists in the class, we're actually going to grab the module and the class name and the function name and then stick that into the global registry. So to see if this works, let's go back to our adder. Import the improved decorator. Also, I don't know why I'm still trying to make these tabs work. All right, make sure this is saved. Okay, now let's try to import again. All right, that imported. We didn't see any exceptions, so that's good. Let's try it out. Two integers, cool. Got a string and an integer, all right. The extended weekly typed adder should be able to add two strings that look like integers, okay. Now what happens if we go back to the parent class and add those same two strings that look like integers? It should just concatenate them. And it does, awesome. I was gonna say guys, don't clap quite yet. Why can't our child class use the parents add method now and just add two integers together? So this is what happens when you just try to slam more code at a problem. To understand this, let's actually look at the MM improved module. What's going on in our registry? All right, so here is the global registry. So now we can see that this fully qualified name, the extended weekly type adder's add method is mapped to this multi-method object. The parent class's add method is mapped to this multi-method object, all right. So let's take a look at the child class first. Type map, all right. So there's the type map for the parent class. Let me see, like the string and string and an int. Arguments are all mapped to these various functions. Now let's check out the child class. And unfortunately our child class only has access to the two functions that it defined. So this isn't great. And again, Python just can't handle multiple dispatch. So my talk is over. No, it's not. Just kidding guys, I like to fool around. All right, let's take a look at this. Okay, hopefully this is big enough and I'm going to not do tabs here. So I'm, I don't know, I'm one of those people that kind of just likes to figure it out myself and I was like planning on writing this whole like multi-method like package and I was gonna put it up on like PyPI and maybe the new PyPI because I saw that talk this morning, that was really cool. But Python is an open source language and I probably would have been reinventing the wheel. So when you actually go to PyPI and search either multi-method, multiple dispatch or overloading, there are so many packages that you can install that can kind of help you with this issue. So I did a little bit of experimentation with, I don't know how many I tried out before settling on this one that I liked the most. But again, feel free to kind of check out the ones that I won't be demonstrating here today. So there's like PolyPI, I think there's some called, like some that are just called multi-method, a few that are just called overloading. So again, like interchangeably using that word like isn't my favorite, but don't sell yourself short by not searching for PyPI or searching for overloading on PyPI just to see how other people are trying to implement solutions to this problem. But the one I liked the most was multiple dispatch two. And multiple dispatch two is just a fork of multiple dispatch. What was added in this fork was support for the type annotation syntax, which I think can look super cool. If you're not familiar with the type annotation syntax, I got really excited when I learned about this because I was like, oh, like static type checking and Python, like this is awesome. Like it's really just like static type analysis so you can like sort of run that over your code before you like even hit the interpreter and it can, you know, point out problems. It's not actually any sort of magic that's happening when you're running your code. But still, the syntax is really cool and I think it can make your code a lot more readable so you sort of know what to expect in your various functions. The way that I'm using multiple dispatch two with the functions that are decorated multiple times, doesn't really support that syntax, so I can't show it off here. But definitely check out GitHub because they do demo that in their readme and I think it looks pretty slick. So, let's walk through this. In multiple dispatch two, you can import this really cool method dispatcher class and basically we are going to be creating our own new add decorator by instantiating this method dispatcher and it's kind of gonna do a similar thing as to what our code was doing before. We won't be maintaining a global registry like with this implementation but we will have all of our various types mapped to the actual function objects that are meant to handle those types. So, here we have, again, this weekly typed adder class instead of the multi-method decorator I'm using this add instance with the register method and that's what's going to take in the object types. Notice my function names have disappeared and now they are just little underscores but that's okay because we can still call add the same way we were previously. And here we have the extended weekly typed adder class inheriting from the weekly typed adder. The thing here is I'm gonna say like this still looks a little more object oriented than what we saw before. Whatever, it's my code demo. So, here I am taking a deep copy of the method dispatcher from the parent class because we don't want to just get an alias of that same dispatcher and then override it which is what we were seeing in the first demo where the parent class then had access to the child classes methods. So, this is sort of a way around that. Again, the function names have disappeared and they are now replaced by underscores but it's okay, we can still call add and the extended weekly typed adder is doing the same thing. It has this weird dicked string addition and also the overwritten string, string addition. All right, so let's demo. Yep, there we go. Oh, that did work. Okay. So, let's see, let's call the adder again. All right, everything imported, so we're good. Now, can we add integers? We can. Can we add integers that are strings that look like integers and just concatenate them? Okay, we can. That's good, thank you. Can we add dictionaries and strings? We can't, which is good because this is the parent class and it shouldn't have any knowledge of that method. And I think this, you know, this is pretty, oh, actually I should probably scroll down a bit so you in the back can see it. Couldn't find a signature for add that takes an addicted string, so that's very helpful. All right, now like fingers crossed for the child class. Can we add integers? We can. Can we add strings that look like integers and get integer addition? Oh my God, we can. And can we do our weird, again, this is like the worst one to sign off on because like why would you do this? I don't know. And you can, so cool. So now we have two classes that are sort of like doing this inheritance object oriented thing with multi methods, but like maybe not really, but it kind of looks okay. So hey, we made it work, that's exciting. And yeah, you can do all of this with the multiple dispatch to package. But again, feel free to check out the other implementations because you know, people are doing all sorts of cool stuff with multiple dispatch in Python. Again, this is just the one I liked best because you can sort of jam it into classes and just make it work. And I'm trying to think if there is anything else. I don't think there is, so I just pop back over to my slides. All right, so that's all that I have. I am going to put the slides and the code samples up on my GitHub tonight. If I don't, you can yell at me on Twitter. Hmm, all right, so are there any questions? I think I actually did finish a little bit early. So yeah, yes, oh yes, okay. So the question is can you use multiple dispatch within a knit method and should you? I have not experimented with that. So I cannot definitely say yes or no. I wanna say that's not gonna work and like no, you probably shouldn't do that. I am curious though as to when you think that would be useful, if you wanna shout it out or we can talk later. Yes, yes, I see what you're saying. Okay, so I sort of prefer, instead of trying to do that kind of constructor thing, sorry, words, in those cases I'd almost prefer to have different just factory methods that can spit out classes based on the parameters that you pass in. Maybe in that case, if it is a factory method that just has the same name, sure you could use multi-methods for that, but specifically for the knit method. I don't really like touching things that have that many underscores. So. That's a good rule of thumb. Thank you, yes, yes. My question is, there's no ABC for file string. There's no ABC for a string. The only ABC is an iterable, right? So there's no, so identifying a string from a list, if you want to stick with Python duck typing, okay? And not compare against a string type, right? Like if you want to be able to accept anything that calls like a string and acts like a string, it's often difficult to make, to differentiate between a string and another kind of iterable, because you can iterate through both of them. Have you, so my question is if you found a good way to differentiate when you're accepting a different kind of things, stick with duck typing, not do an STR check, okay? But like some kind of subclass hook with ABC, or something like that. Oh, interesting. To differentiate an iterable and a string, because iterables and strings act exactly the same all the time, every way except for when you actually try to go to use the string to replace or something or whatever it is, split. So make sure I'm saying this right. I think the question is when, can you, hmm, help me out here. So the question is, is there a good way to differentiate between objects that again, like with Python duck typing? Oh God, I'm having a really terrible time rewording the question that you just asked me, even though it made me, it. So well, I guess in this case, like with iterables specifically, yeah. So, that's the wrong one. Well, yeah. So specifically with what I've shown today, this is going to be like instance checking. So the actual type of whatever you're passing in. The original implementation from the blog post is something that I've played around with to do multiple dispatch on the values of the parameters and not the types of the parameters. So they would effectively be accepting string methods all the time, but it's the value of those string, or not string methods, string instances all the time. But it's the value of those strings that would determine where the call is dispatched to. So in your case, I don't know if I've done anything like specific to what you were describing. Yeah. Oh, yes, hi. So, you know, I'm not only kicking myself for not doing this on any of the Dunder methods. I'm not entirely sure how much magic is tied into any of those just implicitly. It does work, you can, okay. Well, you can do it. I have not tried to, oh, sorry. Oh, I do have, oh, oh, okay. I have zero, but actually five minutes left, sorry. So if it does work, that is awesome. It's not something that I've experimented with personally. I guess I just haven't really found the specific use case where I would want to use the Dunder methods like with multiple dispatch. But yeah, now this is probably something I'm gonna try out later. So, yeah, thank you for your question. Yes. Oh, just, just whoever, just both of you. Go at the same, wait, I'm sorry, say that again. Oh, that's a good question. Yeah, if you have coworkers, definitely do not use multiple dispatch. They're all gonna, hey, you know, this is really just a proof of concept, guys. So, that was actually what I was just gonna say. Like I am, I hate writing doc strings. I hate writing comments. I hate them so much. Your code should just be readable and self-documenting. So if your multi-method names are readable and self-documenting, then your coworkers should be fine and everything will be great. Really, so I'm not, I'm actually not sure how doc strings, oh, yeah, no, I'm trying to like work it through on my head right now. And then I was like, oh, I should try to like live code it and then we should like, yeah, that never works. So maybe I can like tweet out to you guys after this, guys and gals after this once I've investigated. But I think primarily, at least for me personally, unit tests are my favorite form of documentation. So if you have some great unit tests that are testing each of these methods and your unit tests have those like awesome, super long verbose names, so when one fails, you know pretty much exactly why it failed. Hopefully you'll be able to find that in the code relatively quickly. But yeah, your coworkers may hate you. That's like the asterisk I should have put on this. Anyway, your coworkers probably hate you anyway for something else you did like a year ago and you yelled at them on that pull request. So, yes, sorry. Yes, it does. So the method dispatcher that I'm using is, oh, sorry, I'm terrible at repeating questions. Does the multiple dispatch to package work with module functions as well as class methods? The answer is yes. The method dispatch class is specifically for class methods, but actually, you know, I think I might have the GitHub app. Oh, I do. Boop, make this a little bigger. So here is the, if you can see this, here's the much prettier dispatch decorator that is also available in this package. And here you can see it in use with the super cool type annotations syntax. So, yes, this does work for module functions as well. Thank you. Yes. Does it work with mocks? Does it work with mocks? Does multiple dispatch work with mocks? Mock, like, like mocked objects. So are you asking if you, like, if you mock a multi-method? Oh, now I, okay, sorry, now I actually do have zero minutes. Good, because I wasn't sure of the answer. Everyone leave. Actually, that's a good question, I'm not sure. Okay, thank you. Thank you. Thank you.