 Good morning, everyone. Thanks, Pailandim, for this opportunity. Let's get started. Some cheap marketing. I'm one of the co-founders, directors of Centrope Technologies, V2 end-to-end implementations of Integra data science projects, before that I was working with the hedge fund for almost 10 years. And I'm part of the organizing committee of Pyke and India 2018. Most of this works in Python 3, and later I haven't really tested everything with Python 2, but most of it should work. OK, there we go. Let's get started. What are classes in Python? So the way I'm going to run this is I'm going to keep switching between my presentation and this notebook. And I'm going to do some stuff here, and then go back to the slides, and I'm going to link both the things. That's why I'm going to run this entire thing. So before we talk about classes, I'll just get into some practical stuff. Let me create a simple class. I call it class test. No, nothing there, nothing fancy there, a very simple thing. I create an instance of the class, all good. I'm checking the type of the class. It says the class that I just created, test. And then I'm checking the type of the class itself, which is test. It says type, good. And then I also check the type of the type itself. It again says type. So everything boils down to type. So practically speaking, classes in Python are like their first class supported entities. Technically, class is an instance of type. And the classes are themselves like sugar in Python, like where we use class to create an instance of type. Then what is type in Python? So let me look at the official doc of it. For convenience, I have it here. All that it says is there is a signature which says I can use it in two ways. I can try by supplying this an object, or I can pass three arguments to it, which is name, basis, and dict. The first usage of it gives the objects type. The second one helps me to create a type, which internally is a class. So I have this name, basis, and dict. So name is the name of the class that I used to create it. Basis is a list of all the base classes that I want to inherit for this particular class. The dict is a dictionary of the attributes and their values that I want to be available for my usage within the class. A quick example there, like I'm just creating a simple class using the same invocation. I'm just calling type. The first one is a name followed by the object, which is a base class. Just creating it an empty dictionary. Let me look at that type of it. It says type. And then I'm creating an instance of what I just created. And then when I check the type of it, it says it is a class. So all that I have achieved is I have created a class without using the class as an instance of the core type. So this is how I dynamically create a class and also create an instance of it. So this is what I get by type. So type, essentially, is an important data structure. It's a native structure that is used for creating classes. Now let me get into some lifecycle that is involved in creating a class. If you look at it, this is a very vanilla example. All that I do here is I create a test class. I have all the magic methods, which is the dunder new, dunder call, and dunder init. All that I'm doing is I'm just simply putting up a print statement there and calling the super method. Nothing fancy here. So this is just there to illustrate how, when, each methods are called whenever I create something in Python. So all that I'm doing is the class is created. Nothing got printed. So nothing really got executed in this piece of code. I'm creating an instance of the class. So that calls the new method and the init method. So technically, this is the lifecycle. When I request for instance, the dunder new method gets called. Then the dunder init method gets called. Then the instance is returned. Technically, this is the dunder new or the dunder init combination as a constructor in other object-oriented languages like Java and like C-SHA. There is a convenience in Python where we have two methods. One is responsible for the instance creation. The other one is responsible for initialization of attributes within the instance. I'm just doing a simple call here. All that I'm doing is I'm supplying a bunch of attributes to the instance. So this internally calls the call method. I'm executing it for more than one time, second time, third time. Internet registering the call count. I get the call count as three. So the important takeaway, we know what the lifecycle of it is. And we also know call method is class instances are callable wherein I can supply a bunch of parameters and I can get some attributes out of it. We do a quick recap. All that I did so far is class is an instance of type. The type is the holy grail that we spoke about. This is the base of everything that we do in Python. And more importantly, if you clearly notice, I'm stressing it over and over again. Like type is the holy grail and class is an instance. You can further have objects which are instances of the class that is created. Type can be used to create dynamic classes. And we also saw about the lifecycle. Now this is good. Now we have the type. What can I really do with this type? So basically, type is one thing which is used to create a class. So technically, by modifying something in type, I'll be able to change the behavior of the classes. Given the type is native, we can't go and change it directly. However, we can subclass type and do something there. So that is what we call metaclasses. So metaclass is nothing but it is a subtype of type. It helps in changing the way the class behaves. Commonly called as black magic. Somebody said somewhere. And it is used to actually use it only when you actually need it. So a lot of things have been spoken. I'll just do a simple metaclass before we get on to the next steps. So all that I'm doing here is it is a simple metaclass where I try to create a singleton pattern. So internally, I'm writing a metaclass. And then using that in a metaclass. I'll also introduce to the syntax of it. So this is the syntax of creating a metaclass, which is like I create a metaclass. All that I need to do is it needs to be a child of type. And then using the metaclass, this is the Python 3 syntax wherein I supply a metaclass keyword argument and supply the metaclass that I just created. Let me do a simple metaclass now. So the stuff is simple. All that I do is I create it as a subclass of type. And then I override the dundercall method. Internally, I maintain a dictionary. Whenever I ask to create an instance of a class, I just check whether it is available in the dictionary. If available, return it from the dictionary. If not, then create a new one and then put it into the dictionary and return the same stuff. Simple stuff. And now I try and use this metaclass in the class that I just created. So this needs to behave like a singleton wherein if I ask for multiple times the instance of the class, it needs to return me the same instance. So I'm creating two instances of it. Let me check the type and IDs of it. You look at it. The IDs are the same, which means it is returning me the same class over and over again. Let me also introduce you to the big picture of it. So from a big picture, we have the type. And class is an instance of a type. We already established that. Then we can create objects, which are instances of the class. Then what we can also have is a metaclass, which is a child of a type. And we can use the metaclass that we created to create new classes. And that can be used to change the behavior of both the class and the instances of the class. Let me quickly go into the lifecycle of the metaclasses. So we have spoken a lot. I'm just putting in a simple stuff about a simple metaclass, same stuff as I did earlier. All that I'm doing is I'm overriding a bunch of methods, adding some print statements there, and I'm calling the super method of it. So I have dunder new, dunder call, and dunder init. And I created my metaclass. Nothing got printed, so the metaclass is clean. Now what I do is I use this metaclass in one of my classes. And now I kind of create a class. When I do this, I can clearly see it calls the metaclass's new method and the init method. So we saw earlier in the lifecycle, like whenever I create an instance of a class, it calls the new method and the init method. So class being an instance of type, it calls the metaclass's new method and the init method. Now let me try and create an instance of the class. So all the things that get called is I get the metaclass's call method gets called followed by the instances new and the init method. I think pictorially representing this. So we have the metaclass. In the metaclass, you have the new init and the call methods. In the actual class, you have again the new init and the call methods. Whenever the actual class is created, the red ones get called, which is the metaclass's new and init method gets called. And when I create an instance of a class, the metaclass's call method gets called and the actual class's new and init method gets called. I think this is a crux of what we will do primarily with metaclasses. So broad idea, if you really look at it, if you want to change something in the actual class, you need to change it in the metaclass's new and init method. If you want to change something, the behavior of a class's instance, then you need to deal with the call method of the metaclass. So this is what is the broad idea. You change the behavior of a class itself. You use the dunder new and dunder init methods. You change the behavior of the instance of a class. You use the dunder call method. So this is all the theory part of it. This is required to follow what we will be doing further. So I'm going to get into some of the patterns that we will use metaclasses. And we'll see some usages of all the stuff that we saw so far. The first pattern I like to go into is the abstract classes. So what we really do here is we use one of the native metaclasses that is available, which is called ABC meta. So there is a built-in package called ABC, like from which I'm getting the ABC meta and the abstract method. Let me see the doc of ABC meta. So all that it says is it is a meta class for defining abstract blaze classes. Usage very similar to what we have done so far. I create a class, which is my abstract class. And I have the metaclass defined to it, which is the ABC meta. And then init method is just a dummy there. And I have also defined an abstract method used at abstract method decorator there to decorate it and call it as abstract method. So what this does is it says this is an abstract class and there is an abstract method that is available. And by definition of that, I cannot directly create an instance of this class. I can only subclass it and implement the abstract method. Only then will it allow me to create an instance. Let me try and create the abstract class instance. It doesn't work. Now I'm creating a subclass of it. For illustration purposes, let me actually comment out the child classes abstract method definition. Now when I create it, it again says cannot instantiate because my abstract method is not initialized. Now let me go back. Let me remove the comment. I'm able to create it. So this is one fundamental design pattern where you could create abstract base classes. And you could use them to create your abstract method definitions and whatnot. The next pattern we'll be looking at is abstract family of singleton classes. So I used to be a Perl programmer. Then I became a Java programmer. And then for the past four years, I've been coding in Python. The day I came to Python, one of my fundamental questions are, where is my interface? Where is my abstract class? How do I do those things? So this is how we figured out probably we could do an interface with just like. All we wanted to do was we need to have an abstract base class or an interface. I'm talking about the requirement here. And you have multiple implementations of this. And you want each one of them to be singleton. We have seen abstract base classes separately. We have seen singleton separately. One problem with meta classes is you can only give one single meta class as a meta class to a given class. You cannot have more than one. To get around that thing, what we can do is we can combine two meta classes. All that I'm doing is I have a singleton ABC meta, which I inherit from ABC meta. If you look at it, internally ABC meta inherits from type. So I need to override the behavior of the singleton. So basically, the subclass should be a singleton, which means it deals with the classes instance. So I override the dunder call method. And then when I do that, I kind of do the same code that I did with the singleton. I have a local dictionary, which I maintain to hold the classes. And whenever I call for instance, I check the dictionary if available written there or else created and written there. So create this. Now I have my abstract singleton class, which uses this meta class to create the top one, which is the ABC or the interface. It's done. Now when I try and create an instance of it, it fails and says it doesn't implement the abstract class. Now I have a singleton child of it, which is implementing the abstract meta class, also implementing the abstract method. So now let me create two instances of it. Let me check the types. I see the types are matching and also the IDs are matching, which means it is returning me the same instance. So here we are dealing with two things, which is abstractness and also singleton. So this is like chaining of meta classes that we can really achieve. There are more behaviors to be required. We need to be a little careful about how we chain it. We can get that done. The third pattern that I'll talk is about hashable objects. So when I talk about the hashable objects, we all know not anything and everything could be hashed in Python. So the key of a hash, the key that we put in a dictionary should be immutable. So this is a simple pattern, which says it gives you an ability to put any object into the dictionary. So practically speaking, all that we are doing here is we are defining abstract method called getKey, which is abstract in nature. And anybody who uses this as a parent class will implement this. And we are overriding the equals method, which will use the getKey method. So technically, I can use this and create my hashable class. Like the moment I do this, I can get my hashable object. So basically, the keys that I'm using here are just the attributes a and b. So this gives me an ability to reuse the hashableness here where there is a1 and a2. And if you look at it, I'm just comparing a and b, which are the first two values. So the first two ones are not equal. The second and third one are equal. Now I try and put it in a hash map. This works. I'm skipping the comparable ones. I'll get into the pooled objects. So the pooled objects is just an extension of the singleton that we saw. So if you really look at it, the definition is the same. So the requirement here is whenever I supply the same set of arguments for creating an instance of the class, I need to get the same object. I don't want to get multiple variations of it. So all I do here is I internally create a tuple, which is a thing of args and cls. This doesn't work every time because your args could be heterogeneous. So you need to take care of what it is. Advice that I will say is probably you change the args to the actual arguments that you need in your class. Like you create your bean meta. Now you have your bean class, which is using this thing. Now I'm creating four instances of it by supplying four arguments. If you look at the IDs, the first and the fourth one are the same, which means they are using the same class. So this is some kind of a pooled objects wherein you can, it gives me the same instance as far as I supply the same arguments. Other ways of doing this are you could use an object pool manager or you can also use a moist function, which could actually help in getting the same stuff. The last pattern that I'll get today is with sealed classes. So in sealed classes, the requirement is I just want to know more subclasses of this particular class. And the subclassing should end with, so I don't want anybody else overriding or subclassing this and overriding behavior. I need this to be the bottom of the tree. So implementation is, again, I get the basis. For each of the basis, I get what is the meta class of that particular place, which I get by calling the type. And when I do this, I get all the meta classes that is used in the entire chain of MRO. Now I can check whether my sealed meta is available in the meta classes. If it is available, then somebody is kind of using this class as a parent, and then I raise the error. So this is what it is. I create my sealed class, like supplying this as a meta class, all good. Now when I try to override the sealed class and create a child of it, it says an error saying that you can't instantiate a sealed class. So basically, there are more behaviors that we can actually do this. We're running out of time. Otherwise, I would want to show this one thing, which is logging using meta classes. So technically, what I'm trying to do here is we kind of use the meta class to log every function that is called. We can use it again with the call method. We can use it again with the DunderNew method, apply a decorator to each one of them, and we can get it done. More things can be done in meta classes. Thank this opportunity to give me this opportunity to present this thing. There are some references. There is one video metaprogramming by David Weasley, wonderful thing. I think it's a three hour long video. Everybody should see that it's quite enlightening when it comes to meta classes. Obviously, practical Stack Overflow gives a lot of supports. Ready to take any questions if you have any. Thank you. So he's so kind, then he's going to ask a couple of questions. Yeah, over there. Nice talk, by the way. Thank you. With the keys and hashable objects, you said something about putting the objects in hash maps, but you actually never showed what happens if they had equal keys. So Python mandates that you need to have the keys of any dictionary to be immutable. Because if it is mutable, the hash code is computed once. It is the same in every other language, because if the object is mutable, then the hash code changes. You recompute the hash, it is not possible. So the only way you could do is keeping the object itself immutable. What I have tried and done there is saying, this is what my keys are. And use that to actually hash it into the thing. I'll probably share this notebook you can take a look at it and see. Thank you. Does it? Thanks for the talk. Thank you. It's called practical patterns. And I look at some of them, and I think they are very illuminating how the language works. But when I look at something like a sealed class, I think like, would I ever use it in an application? Would I ever be so, so not trusting of my fellow developers that I would say, look, this is a sealed class. Don't touch it. So do you have a motivating example for that? Or where would you actually do that? Basically, sealed classes, I thought it would be easier to illustrate here. But a more practical usage which could be used as an extension of the sealed class that you saw here is you sometimes don't want to allow your clients to override some methods. So basically what you can essentially do is instead of stopping them totally from using this as a parent class, there are many instances where you don't want somebody to override the methods. You could use this idea. This is just an idea. You could use this idea, extend it a little more to achieve that, which has far more practical usages than the sealed class itself. So one more question. I was just going to ask because you came from Java to Python, would you ever go back now or not? No, I came from Perl to Java to Python. So I definitely missed Perl when it was in Java, and I found the answer with Python. I found peace with Python. I'll stay with Python. OK. So one last thing, I'll just do this. I'm also a co-organizer of Pyken India. Pyken India is happening from October 5th through 9th in Hyderabad, India. This is the 10th anniversary of Python India, and the CFPs open, the tickets are open. These are the keynotes because we have for the event, and you can find more details at indodpyken.org. And hope to see many of you there. Thanks a lot for the opportunity. Thank you. Thank you very much.