The Ultimate Guide to Writing Classes in Python

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
a while ago I did a video called The Ultimate Guide to writing functions which you seem to like a lot so today here's the next installment The Ultimate Guide to yes writing classes and in this episode Harry and his friends are trapped in a world of abstract classes and protocols unable to instantiate objects Mischief Managed now let's dive in before I accidentally turn into a data class I don't even know what that means by the way you may have noticed that I have a new logo since a couple of weeks and I want to show you something really cool here take a look at this yes that's a neon light I'm gonna put this in the main room in a couple of days I'm really excited about it I think it's gonna look really sick so anyway I just want to share this with you back to the video first but also the most obvious thing to take care of is to make sure that your classes are not too large as an example I have a personal class here which has a whole bunch of instance variables and next to that it also has lots and lots of methods which is not a really good thing if you have a really large class like this you're going to lose overview pretty quickly this example by the way is really basic just a person Class A couple of methods and I have a main function where I create one person and then I print some information so I can compute a BMI which actually is not very accurate but for now it'll have to do and I'm going to update the email address updating the email address means that we're going to also send a message to that person that the email has been updated so when I run this then this is what happens so we get a BMI value we get a BMI category and we get an email has been sent successfully confirmation I'm not actually sending an email because then my mailbox will probably overflow with lots of test messages but if you basically uncomment these two lines and this is actually going to send an email how do you make sure your classes don't become too large well you have to think about splitting things up more and a good way of approaching that is by thinking of classes as being either data focused or being Behavior focused data focused classes are mainly intended as let's say structured information structured objects whereas Behavior focused classes are really about grouping methods that we long together that doesn't mean that a data Focus class shouldn't have any methods whatsoever or that a behavior Focus that shouldn't have any instance variables obviously that's the whole idea of a class that you can combine the two of them but by focusing either on data or on Behavior it becomes a bit easier to split things up let me show you how that works here so if we go back up to the person class we see that there is actually three different things in the person class so that's the person information like the name and the email address there is an address which is basically of these things together and we also have some stats like the height weight block type eye color etc etc etc so what you could do is split up person into three different parts one is the actual person class so we're going to keep the person class but then we're going to split out the address which is a data Focus class and we also split out these stats which is also a data focused class so the first thing I'm going to do is Select these lines which is everything concerning the address and then I'm going to create a new data class that contains all of these things and now instead of putting these things directly in the person class we can now add an address object like so and of course we need to change a couple of things here because now this is no longer correct so I need to take these lines right here and we're first going to create an address and we're going to pass it to person like so by splitting the address from the person we now also have to look at the methods and there is one in particular which is this get full address which is basically a way of providing a string version of the address so we actually also should move this method to the address class as well and now this we delete you can already see this helps a lot in making the present class smaller because all of this information the data here and a couple of useful methods like get full address are actually part of a separate class we can do the same thing for these stats like the gender height weight etc etc so let me copy these and now I'm going to create another class and pasting all of the stats in there and we're also going to copy the page and in the person class we can now remove all of these instance variables like so and I'm going to add a stats object and now again we're going to have to clean up the methods a bit so now we have get BMI and get BMI category that basically should also be moved over to the stats class and then of course we have to clean up our main function as well so I'm going to create a stat object and copy over all of this information and then of course we also have to provide the object as an argument to the person class like so yes we also need to change so now the person class is much shorter and we have split things out into a separate address class and a stats class and all of these three classes I would consider basically more data focused they're really aimed at representing information and they have a couple of methods that access that information easily there's one thing I'd still like to clean up though because person is a data focused class but there is also a behavior which is updating the email address and this method currently does too much because it not only updates the email address but it also creates an email message it connects with an SMTP server and then sends an email so that's too many things a better way of setting this up is by splitting out this Behavior which is not really part of the person class and turn that into a separate class that you can then use right here here's an example of how you could do that so I've created a separate class called email service which gets all the information like the SMTP server the port the email that you use to log in with the password and it has a sent message method that creates the message and then sends the email and I've put this into a separate package called email tools so what I can do is import that package and import the email service and now instead of person having to create the email message itself and doing all the low level connection stuff we can actually replace this now by creating an email service and here we provide the STP server Port email and password and then we're going to send a message and I can basically delete all of these things like so and now it's nice we also don't need these low level SMTP lab and email message Imports anymore so let me run this after the changes we should get exactly the same and now we actually see there's two messages because I have already have the login message here so I should probably remove it from the person class like so so instead of having one huge person class we now have four actually Four small class we have two person class which only contains a few methods we have the address class we have the um stats class and we have an email service class and what's nice is because these classes are now much smaller we can also reuse them in different places if for example we have another area in our application where we need to send emails well we can simply use this class and that's one big advantage of splitting things up more second thing you should do is make your classes as easy to use as possible and one way to do that is by relying on properties and Dumber methods so an example of a class where you can improve things is stats so stats has currently a get BMI method and a get BMI category method actually both of these can be properties and the advantage of properties is that you can access them like you would access on instance variables so you don't have to write the parenthesis at the end and that's going to Simply compute the value for you on the fly so the way to do it change is actually really easy so if we have Gap BMI we have to add a property decorator above it and then the get underscore part we can simply delete because that's typically not what you do when you have properties and same with get BMI category so also here we're going to add the property decorator I'm going to remove the get underscore part and you also see that we get some errors now because obviously we changed this method into a property so instead of calling a method like this we can simply write self.bmi is less than 18.5 Etc now we also change it over here and over here and then if we scroll down then we have here BMI equals stats dot BMI like so and same for the BMI category using properties makes our code way simpler so here we don't even need to store this in a separate variable we can simply write stats.bmi and remove this line altogether because it's not really short to write this and there are more methods that can be updated to a property for example split name could also be a property and now we want to have the split name we can simply call person dot split name without the parenthesis for the address you could turn get full address into a property which is totally fine but actually the only thing this does is return a string representation of the address object and so instead of having a separate non-standard method for this you could also use the Thunder string method note that data class already generates a wrapper method which is a string representation of the object that's mainly useful for Developers for example if you want to serialize the object to file and then read it again later to recreate your object the SDR dummy method is similar to wrapper in that it creates a string representation of an object but this one is not aimed at developers but at actual users data classes doesn't generate that for you but we can do it ourselves like so there's few minor things we can still improve here one is that BMI is being computed on the Fly which is great it's a property but if we call it here in BMI category then every time we call self.bmi we're recomputing the BMI value which is not necessary because we're not changing any of the stats so there's no need to do that over and over again now one way to solve that is actually turning BMI into a cached property and that's also really easy gas property is something that's part of fun tools which contains a bunch of other useful things as well show a couple of more examples of what you can do with funk Tools in a few minutes but now because it's a cached property we're only Computing this once which is really nice because then every time we call this then it doesn't have to recompute the value it will use the cached value however this actually potentially leads to a problem which is that if we change the stats like we change the weight for example then the cached property is not being recomputed so that's dangerous one way to solve that is to turn BMI into a function and while we're at it we can also change BMI category into a function so let me show you how that works so I'm going to move the functions right here now BMI needs to wait and height and be my category needs a BMI value like so and then here we're going to compute the BMI value which is BMI stats weight stats height like so and then here we compute the category based on the BMI value and then this is what we get this one is no longer needed let's run this one more time we still get exactly the same result however now we lost the advantage of using a cache but fortunately fun tools also has a way of caching function values and we can use least recently used cache LR lru cash that's also from fun tools and we can do the same thing here the nice thing about least recently used casters that it's going to keep a mapping from the arguments that we provide to the result so for a given weight and height it's going to Cache the value for us and same for the BMI category so it's going to maintain a mapping of BMI value to the actual result of the function and now it doesn't matter anymore if we change the stats height and weight and then we recompute the BMI like what we're doing here then it's simply going to use either a cast value or it's going to recompute it based on the new value which is exactly what we want the third thing you should do to write great classes is to rely on dependency injection and not have a class create other objects if that's not absolutely necessary in this case the person class when you update the email address is itself creating an email service object which is not great because that means that if we want to test the person class then we're going to need to patch the email service object here which is Annoying It's much cleaner if the email service is provided as an argument to update email so that update email only has to concern itself with updating email and then actually sending the message so let's change that so I'm going to provide an extra argument here called email service and that's of type email service obviously so now this creation logic here we're going to move that to the main function and then when we update the email address we're going to supply it as an argument like so now this may look like a minor change but this actually makes the person Class A lot easier to deal with because now whenever we create a personal class we want to update the email address we can supply a custom email service which is nice because if you want to use a different service in different circumstances we now don't have to change anything in person class we can simply Supply it with a different email service you could even consider making email servers an instance variable if person class has several methods that need to send emails then this could be a way to only provide it once when you create the person and then after that you simply access that instance variable and then you don't have to pass it to every method call one thing I would still like to improve is that currently person is directly dependent on the email service type and in the future we might want to replace it with something else instead of defining directly that this is of type email surface we can also introduce abstract and decouple the person class completely from the email service class to do that let's create a new class called let's say email sender and this is a protocol class you can also use ABCs obviously but I like protocol classes for this so I'll just use this and then that's going to have a send message method that's the only method that it's going to have which looks exactly like the send message in the email service class so we have two emails subjects and body and here two emails or Tech and body and now instead of saying that this is an email service object we say that this is an email sender object and this still works without any issue because obviously we didn't really change anything we're just being a bit more explicit about the types and we don't have to change anything here but now person class doesn't need to know anything about the email surface the only thing it needs is an object that has a single method called send message and here in the main function we're actually patching everything up this also makes the person Class A lot easier to test because now if you want to test updating the email address we can simply create a fake email service object I mean as long as it has a send message method done we're good to go basically and we don't have to do any patching or any tricks to make sure a person is easy to test the fourth thing the fourth the fourth thing to Fourth thing the fourth thing the fourth thing the fourth thing to think about when you're dealing with classes is whether you really need a gloves and that may sound weird but actually class is mainly useful especially in Python if you need to create multiple instances of it if you only need to ever create a single instance of the class and you know that beforehand then it's actually better to just use a module in Python with functions because that's in the end a really great simple mechanism and then you don't need a class and a good example here is the email sender actually because the email sender class we don't need multiple email senders at least I don't expect we will so we can actually decide to change the email sender to a module with functions and don't use a class at all so in the original email surface class we have a send message method to email subject and a body and that uses an SMTP access point so here's a new version where I've actually split this up into two parts so I have a sent email function that gets everything it needs SMTP server Port email password the subject body of the message it then calls a create email message function that creates the object sets the content and then returns that message and then it connects to the STP server and sends the message if you uncomment these lines so what we can do is instead of having to create an object we can now simply call a function so let's change this argument to be an actual function and not an object an email sender will change that to be a function type and not a class type one way to do that is by defining a callable so you could do something like this and then the callable will get three strings three strength three strings strings three strings okay I guess I'm getting a bit tired and then I'm going to comment this out I'm going to use this later so now email sender is a callable with three arguments that returns Nom and then of course here we don't do send message but we simply call send message function directly and that gets an email it gets a subject and gets a body now the disadvantage of using a callable like this is that we can't define the names of these parameters here there's simply strings we don't know what is what so one way to fix that is actually not using it callable to define the type but still use a class and what you can do is turn this into a call Dollar method and now we have the call Domino method defined we have defined the arguments to email subject and body and email sender is now a callable because it has this called donor method defined so this also works and then we don't need this import anymore obviously there's one issue in the new version of the service send email needs an SMTP server a port etc etc and our email sender function doesn't have those things again Funk tools to the rescue because we can use partial function application to fix that so from the new version of the service we're going to import send email which is the function that we're going to need and then we're going to use partial function application to already Supply the SMTP server settings so I'm going to create a sent message function which is a partial application of send email that gets the SMTP server and I'm going to use keyword arguments here to make sure that this works as we expect foreign person gets sent message as an argument like so and now let's run this code again I think the issue is that we also need to supply the keyword arguments here like so try this one more time and now we get again the same result that we had before so we went from this huge person class with tons of methods that we had in the beginning to nicely separated classes that group data and a couple of useful properties and methods that do something with that data and I've also changed the code to use functions wherever appropriate and not using classes everywhere because that's not needed in Python so it's actually kind of weird that any guide to help you write better classes one of the tips is actually to not write a class but use functions but it does make a lot of sense final thing I want to cover before I finish this video is to talk briefly about encapsulation so encapsulation basically means that we want to make sure that some information is hidden encapsulation is one of the big advantages of writing classes because they allow you to sort of Define a wrapper around your data so you define your data as instance variables and then you use methods to modify or to read that data I have a final example here to show how encapsulation works and in this case I'm not using data class because data class don't really support encapsulation and python actually is not possible to Mark attributes as private because python doesn't have access modifiers but there is a way to do it that's sort of approaches the behavior of defining private attributes and that's by using a double underscore in front of the instance variable name like I'm doing here so here I'm creating a person that has a name and an h and a social security number and the social security number I don't want it to be available publicly so I'm storing it in a private attribute and I'll talk about how this works in a minute and then I have a property called social security number that actually masks the actual Social Security number so I create a person with a social security number and then I'm displaying the information that uses us this property so when I run this then this is what we get so we get a masked number here and you can actually see that if I try to access this then I'm going to get an attribute error that person has no attributes Tumblr SSN so in this sense it works like a private attribute if you try to access it directly like this it's not going to work however it's not actually private because the only thing that it does is if you use the double underscore it's going to mangle the name and it's going to use some internal name and actually you can fake that so here I have a mangled version of this private instance variable and you can see that when I run this it actually prints the Social Security number so it's not private at all because I can still access it but the name is mangled so it sort of behaves a bit like of private instance variable I hope these tips help you write better class so keep your classes short think about whether they are more data focused or behavior focused use properties to make your classes easier to use inject dependencies instead of creating objects inside a class only use classes if you actually need to create multiple instances of a class otherwise in Python simply use a module with functions and finally if you have lower level data structures in your class make sure that they're encapsulated so that the outside doesn't need to know all of the implementation details if you also want to learn more about how to write functions properly then you should watch this video next thanks for watching and take care
Info
Channel: ArjanCodes
Views: 43,864
Rating: undefined out of 5
Keywords: python classes, dataclasses guide, dataclass guide, writing data classes, how to write data classes, dataclass, dataclasses, dataclasses in python, dataclass python, data classes python, python programming, python classes and objects, object oriented programming, python class, classes in python, dataclass vs pydantic, dataclasses arjancodes, dataclasses python, python classes explained, python objects, python programming tutorial, classes python, python programming language
Id: lX9UQp2NwTk
Channel Id: undefined
Length: 25min 39sec (1539 seconds)
Published: Fri May 19 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.