Understanding decorators [Python tutorial]

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello there in this tutorial we are going to cover decorators which are a somewhat more advanced topic in python so let's go over some theory first in the most basic sense decorators are functions that decorate other functions which does sound a bit cryptic but essentially what we do is we are wrapping one function around another function and let's do this a bit more visually to illustrate what's happening this is a normal function and usually what you do is you just call it somewhere and then you have something happening what decorators do is we still have the normal function but now we are putting another function around it and then when we are calling all of this we are calling the decorator function and inside of that decorator function we are calling the original function and this is allowing us to execute code before and after the function so when i'm calling this function here i could run code here and i could run code here without making any changes to the original function this function here would stay exactly the same all the actual logic happens inside of the decorator we are literally putting one function inside of another function and this other function executes extra code that's the entire idea of a decorator now why would you want to do this basically this way we can give extra functionality to a function without changing it a simple example here is we could write a decorator for a function and it makes the function execute twice when called not the most useful function but we are going to talk some more useful examples later on i just want to illustrate what is happening now in practice you are seeing decorators in three different circumstances let's go over them the most common one is you want to test your code without changing it let's say you work in a team you have some really complicated code and you want to test your code without making too many changes a decorator would be excellent for that the other example would be you are working in a team and you want to avoid making unnecessary changes now those two points are pretty similar you basically want to do something with your code without making too many changes and both of these are also fairly advanced if you are just learning python you are quite far away from either of these which is why you may be struggling with decorators because you don't really need them however there is a third case that you probably are going to use and that is using a decorator inside of a class because this allows you to run code when an attribute is accessed or changed so for example let's say you have a monster with health and every time that health is changed you want to run some other function inside of the monster with a decorator doing this is fairly easy although that being said decorators can be quite hard especially for beginners because we are going to use some more advanced functionalities in python that may be slightly confusing although i would really recommend you to try to follow along because this is going to be really good practice to understand functions and how to pass them around in fact before we start with decorators we need to recap functions itself so let's have a look at that here we have a completely empty sheet of code and let's just get started with a really basic function i want to define let's call it func there are no arguments and inside of this function i just want to print function just about the easiest function you can write if i call this function i get function so this really doesn't do all that much now there's a really important concept you have to understand that right now we are calling the function here and if we didn't do that so if i remove those brackets i could run this and nothing would happen but what i could do is print the function itself and now i get a function object meaning this function here as the whole function not what's being returned just the function itself and this is just going to be an object if that doesn't tell you anything check out my videos on object oriented programming they should be quite helpful but basically what this funk is giving us is a simple object that we can call and if we don't call it we are just getting a function object still not particularly useful but what we can do with this function object is pass it around like any other object like an integer like a string like basically any other object we can pass it around for example what we can do i can create another function and this let's call it a wrapper because this one is supposed to take the argument of the function and then i want to execute the function itself meaning what i can do now i can call my wrapper function and run this and i get function again and what is happening in here i am calling this wrapper and i'm passing in this function here what happens as a consequence is this function is being passed as a parameter into the wrapper function and inside of that function we are calling this function and when we are calling it we are printing the word function and now that we have that we could also do something like print hello and print goodbye and if i run this now we are running the function inside of another function and before and after we are calling it we are running some other code and this is basically the main idea of a decorator although it does get a bit more complicated but we'll come to that in just a bit now there's one more thing that you can do inside of a function and that is you can create a whole new function so for example let me create another function and let's call this one a function generator and then here we have no arguments to keep it simple and now inside of this function generator i want to create a new function and let's call this one new function i guess it's a good word and in here i just want to print new function and since this new function is just going to be an object we can return the new function and what i can do with that i can create a variable let's call it new function and i want to get my function generator and if i run this we can't see anything but now this new function is going to have this new function in here meaning what i can do i can call my new function execute it and we get new function so just to go over this because it may be a bit confusing this new function executes this one here and what we are getting from this function generator is we are defining a new function this one here and at the end of the function we are returning this new function and this we are capturing inside of this new function variable and this new function variable now is just a function meaning we can call it here and when we are calling it we are getting this print statement that's really all that's happening here the main thing you have to understand is we are basically passing around functions with the return statement with that we have some function basics so now we can actually start working on the decorator and for that let me open up a new file and in here i want to create an actual decorator now first of all for decorator we are going to need a basic function we want to decorate so from my function basics i want to copy this basic function here which is just printing function by itself so if i run this we get function literally the easiest function you can write more or less and now for this function i want to create a decorator and this is going to be another function that you can give whatever name you want let's say i want to call this one decorator to keep things simple and this decorator is going to accept one parameter which is going to be a function so in just a bit we're going to pass this function inside of this decorator what we are going to do with that inside of this function we're going to create another function and let's call this one the wrapper and this one right now doesn't have any arguments but now inside of this function i want to run the original function and besides that i can do quite a few other things for example i could just print the decoration begins and let me write this properly and i can also write the decoration ends and now after i have done that i can return this wrapper and now what i can do i can create my new function and this is going to be my decorator and instead of this decorator i want to pass in my function and once i have that i can just call my new function and i get the decorator begins the function and the decorator ends what is happening here let me go over this this is probably a bit confusing we are starting with our basic function and this function we are passing in here into the decorator function and this is the one we have up here and inside of this decorator function we are creating a whole new function this bit here and inside of this function we are calling the original function meaning this one here and then around this function we are doing some other stuff like we are printing these two statements here and then at the end of all of this we are returning this new function and this new function we are storing in the new function variable and then we can just call it like any other function and now we have extra functionality around our original function now where this becomes really interesting is let's say somewhere later in the course i am running function by itself which would just give me the word function let me comment out these two lines here if i just run fung i get function and nothing else and what i can do with decorators if i uncomment those two lines and get rid of this new func when i create the variable name for this i can just call it the same way like i have called the original function so i can call both func if i do that and run the code again i now have a new functionality for this function because we are essentially overwriting the original function name and that way our function even with the same name has new functionality and this is the basic idea of a decorator if we have this kind of system we can have really complex code and still check our function without making any changes to the code and since this is a reasonably common operation python has a shorthand for it and let me comment out this statement here and the shorthand looks like this i first write an add and then the decorator function so in this case decorator and now if i run all of this again we are seeing the same outcome for the simple reason let me uncomment it that this statement here and this line here they are doing essentially the same thing we are wrapping this function here inside of a decorator this one here and let's go over this a bit more slowly i think that's going to be useful here we have the function you have just seen and you can wrap this in a traditional way by writing the function and then we are putting the function inside of the decorator and then we are calling the function i hope this is making sense if you're confused about this i would recommend to pause the video now and just go over all of these different statements and see how they connect i guess i can go over it as well again we have this decorator here and inside of this decorator we are passing in the function and then we get this function here as a parameter now inside of this function we are creating a whole new function and this one is called wrapper and only inside of this function we are calling the original function this one here and then around this function we can do whatever we want like calling these two print statements here we could also do lots of other things i'm going to cover that in just a second and finally at the end what we are doing is we are returning this wrapper function and this wrapper function we are storing right now in func and then we can call it like any other function and that way we're using the name of the function and overwrite it with this new function that is being wrapped inside of the function which means if i took out this line here the function would come back to its original functionality and just print function now the problem with that approach is that we are writing func three times one two and three and if you want to call it a fourth time which is kind of annoying so what python developers have done is to create a shorthand for this and this is called a decorator and this one is looking like this and basically this line here where we have the name of the function a decorator and then the function as an argument is the same as this add decorator those two lines are doing the exact same thing meaning when you are calling this function you have the same result now obviously just printing two more statements isn't particularly useful so let's actually do something more relevant to see why decorators can be useful here we are back in the code and i want to create another decorator and this one i want to call the duration decorator and then here again as a parameter we need the function and inside of that i want to create a wrapper it doesn't need any arguments and in here first of all i want to execute my function and then return the wrapper and this is essentially a decorator that doesn't do anything right now and what i want to do with it is to measure how long it takes to execute this function here and for that to work we need a time module meaning i want to import time and now what i want to do inside of this decorator i want to get my start time and this i would get with time dot time and this has to run before i call the function and after i have called the function i want to get my duration and my duration is my time.time which is my current time and from that i want to subtract my start time and once i have that i can print let's call it duration and this should be an f string with the duration and now just to get some reasonable numbers in here when i am calling the original function i want to call time dot sleep and make it sleep for one second and now with this duration decorator let me actually call it so duration decorator i can now comment out this line here and now if i run the code we get function and we get the actual duration and from this line we know it took one point a tiny amount of time to execute all of this and this now we could get without making any changes anywhere else in the code we're just calling the function here we have the wrapper up here and then we're adding this one line of code to add some debugging functionality so this is something you could be doing if your code is running very slow and you want to identify what is slowing it down now another thing that i haven't covered yet is that you can combine different decorators and this happens by just adding them with the different add statements on top of each other and we already have another decorator so let me call this original decorator and let's run all of this again and now let me go through them i have decorator begins and decorator ends that is what we are getting up here from this original decorator besides that we have the duration of the decorator and this is what we are getting from this print statement here and the actual function so this print here is this line meaning now even though we have a very simple function by using decorators we can give it a ton more extra functionality without making any changes to it which is well the entire idea of a decorator if you understand this it can be really useful in very specific circumstances although if you are a beginner you probably just are going to add more stuff to this function although well if you know decorators you don't have to and this makes it much cleaner to work with your functions but alright let's do another exercise on this i want you guys to create another decorator that calls the function twice and this decorator should be called with this decorator and this decorator as well so that this function has three decorators in total and let's see how far you get again i have to create another function and let's call it the double decorator and in here once more we need a function and inside of this function i want to create a wrapper it doesn't need any arguments again and now in here i want to call my function twice which i just do by calling my function twice it's very simple once it comes down to it and once i have that i want to return the wrapper once more oh and by the way i'm just calling this wrapper because i think it makes sense you could call this whatever you want there's no naming restriction and all right now what i can do i can add another decorator with the double decorator and now if i run out of this we have to wait a second and if i expand this we have the code being executed once and then twice and well this is working really nicely and here again our actual function is really simple and we are just adding more and more stuff to it and that way it gets more and more complex with that we have some very basic decorators now there are some more things we can do with them and what i want to cover for this part is that you can have functions with parameters being decorated that part is actually really simple and besides that decorators themselves can also have parameters although implementing this gets even more complicated because we are wrapping a function inside of a function inside of another function so this we will have to explore in quite some more detail but let's have a look at all of this here i am back in a completely empty sheet of code and i want to create a new function so define function and now i want to have a function parameter it doesn't really matter what it is and in my case i am just going to print that function parameter and now once we have that i can call this function with whatever i want let's say hello and if i run this i get hello a super simple function and to decorate this we kind of need the same thing we have done here it's not really that much more complicated as a matter of fact let me actually copy this decorator here and paste it in here and now i want to decorate this function with the decorator and just as a reminder this would be the same thing as calling function is the deck curator with the function however now if i call this i am getting the decorator takes zero position arguments but one was given and this happens in this one as well as in this one so we're getting the same error message that's usually a good sign at the very least the problem python has is that this function needs this parameter so when we are calling the function up here we again need the parameter but on top of that when we call this function here we are calling this wrapper and right now when we are calling hello we are passing it essentially into this parameter here which doesn't exist yet which is why python is so unhappy and to get around this we just have to create the parameters meaning this wrapper here and this function need the same parameters oh well for this wrapper it's a parameter for this function it's an argument and let me name them properly here i want to have a wrapper parameter and now if i run out of this again we should see a different error message and that is that funk is missing one required position argument func parameter meaning this one here and well i get this argument from the wrapper parameter so now if i run this we get hello and if i change this argument to something this is still working and we could also do this the other way the more traditional way and we will get the same outcome and let me just go over what is happening here by using the traditional style to make it a bit easier to explain we are again calling this decorator function here and passing in the function itself now inside of this decorator we are creating all of this here and right now the wrapper has one parameter the wrapper parameter and then inside of this wrapper function we are calling whatever code we want and we also have the original function here and then we are passing the wrapper parameter as an argument inside of this function and once we have all of that at the end of the code we can just call the function with something and then this something would be passed into the parameter for the wrapper and then this would be passed into the original function now what you see very often for a wrapper you don't have one specific argument instead people very often just add arcs with a star and then double quarks and then this you pass into whatever original function you have so if i run this we get the same outcome and these are very simple unpacking operators this one is for a list and this one is for dictionary or keywords and the reason why these are used is because they allow for this decorator to accept basically any kind of function with any kind of parameter right now we only have one parameter but we could also have more name parameters an unlimited amount of arguments it doesn't really matter if you have this setup you can accept any number of arguments or named arguments which means that this decorator works with literally any function without limitation and all right with that we have basic parameters what we can also do let me uncomment the decorator let's try if this is working it does what we can also do is create a decorator that is accepting other arguments like a 10 for example but for that to work we need something even more complex so let's do this in a separate file all right and here once more we have an incredibly simple function if i call this function we can see function and now what i want to do i want to create another decorator and let's call this one a repetition decorator and this one should accept one argument by how many times i want to repeat this function here for example if i add a five in here i want to repeat this code five times when it's being called or rather i want to call it five times when it is being called now for that to work we need some pretty extensive setup and let me go through what this would look like i am first going to create the actual decorator so repetition decorator but this one is not going to accept a function instead it is going to accept this argument here so in my case i want to call this the repetitions and now inside of this i am creating another function and this is the actual decorator and this decorator is going to accept a function and now inside of this decorator i want to create the actual wrapper and this one in my case has no arguments and what i want to do is for let's call it r in range repetitions i want to run my function and now what i have to do i have to inside of the decorator i want to return the wrapper and then inside of the scope of this repetition decorator i want to return the actual deck curator and now if i run this it is going to work five times and i can change this to a 20 and we will call this function 20 times now the question is why does this work so let's go over it and let's do this in a traditional way which i guess i haven't done yet but if we didn't use decorators and still wanted to use this it would look like this i'm still using my func variable name and now i want to call the repetition decorator and now for this repetition decorator i have to specify how many repetitions i want let's say 4 in this case and after i have that i am calling whatever i get from this and passing in my function and if i run this we get function four times now if you look at this this is probably really confusing but to understand this you basically have to understand the return statement and let's go through it one by one first of all we are calling the repetition decorator and passing in four that is this line of code here and this one by itself doesn't actually do all that much all we are doing is creating a new function and returning it so all we are getting from this repetition decorator with a 4 is this decorator here a new function meaning this decorator here is going to replace the entire decorator we have seen so far and on this new decorator we are calling the new function this one here meaning now we are passing this function inside of here and this function is being captured inside of this parameter and once we have that we are basically back to a normal decorator so inside of this decorator we have a wrapper and this one just loops over the range of repetitions that we get from up here and then inside of that we are calling the function and then we are returning the wrapper and then this wrapper is being stored inside of the function and we can call this function and then we are repeating it by whatever we specified in here and well if i comment this out again and return to my decorator let's add four in here and we still get four repetitions and i would really recommend you to practice this in your own time although if you're just starting don't worry too much about it just get used to functions and classes by themselves and once you are a bit more comfortable start working on decorators they are definitely one of the more advanced topics in python although there is going to be another section and this is where you are probably going to use decorators the most as a beginner and that is to use decorators inside of classes so let's talk about decorators inside of classes and there's one really important one it is called property and what this allows us to do is it allows us to turn methods into attributes and this is something we get from the property function that we can use inside of python it actually comes with python and i'm pretty sure all of this is going to sound really cryptic if i just explain it theoretically so let's do all of this in code that should be better alright here i have a completely empty python file and what i want to do is to create a new class let's call it generic and in here i want to create a dunder init method and this one itself and nothing else and what i want to do in here is create one attribute let's call it x and let's set a value for 10 it really doesn't matter what it is and now i want to create an object from this class let's call it generic and i just want my generic class and now i can print generic dot x and i should get 10 and indeed i do and now what i want to do is i want to observe this x here meaning whenever i look at it so i retrieve the value or when i change the value i want to run some other code and this by itself in python isn't really that easy to do i guess what you could be doing is run some kind of function that let's call it get x in here self and in here i could return self.x and then besides that i could print some other code let's say get x and now inside of this print statement instead of x i want to get x and don't forget to call it if i run this i get get x and the value so when i get x i can run some other code and this is fine by itself but it's not very elegant and again like we have seen with the other decorators what happens if i already have a lot of code let's say i have this generic x 10 times in my code and i don't want to change all of these instances here i just want to change my class itself to account for that that whenever we get x i want to run some other code and this we can do but we need some special function in python and this function is called property and into this property function we can pass in a getter a setter and a deleter and then we can assign all of this to one attribute inside of our class let's say x right now and then this x is what we are actually going to work with so now when we are calling generic x this line here this x refers to this x here and then when we are just looking at this x we are calling the getter method when we are changing this x we are calling setter and when we are deleting this x we are calling deleter meaning now what you usually see with this the original attribute we have this x here we are turning into a private method with an underscore before it and now to python this x here and this x are two separate variables but i as a programmer still know that they're the same because of the naming convention here and now basically what i'm going to do for this getter i am going to return underscore x and with that we have a getter so this method here is going to be this getter meaning whenever we are calling this generic x we are calling this get a method which is then returning this underscore x and essentially what we are doing here outside of the class we are working with x and this is what we are accessing but inside of the class we are using underscore x and this is what actually keeps track of the value and now basically what we have to do besides a getter we also need a setter so let me change it and for the setter we need self and a new value and when we are calling this let me run set x and then self underscore x should be the value and this we do not want to return and finally besides that let me copy it one more time i want to have it deleter this one doesn't need a value and what i want to do in here is delete x and all we have to do for this one is to run del and self dot underscore x and now i can run this and let me make this a bit smaller now when i print generic x we get get x and the value of x meaning we have run this method here what i can also do before i'm printing x i can run generic x is equal to 4 and now if i run this i am setting x and i'm getting x and then i get the actual value of x and finally what i can do i can also run delete and generic dot x and now if i run this i have set x get x the value of x and delete x meaning now whenever i for example get the value of x i can run whatever other code i want and for example what i could do with that i could import from date time that's not how you spell that from date time import date time and i want to know whenever i get x which i get by just printing the date time dot now and now if i run this i am getting the current time when i'm accessing x and since we're just returning this underscore x you could even make changes to whatever x you get depending on the time of day some other variables some basically anything you want to look at which is why all of this is really powerful and i would recommend to go over this in your own time it's really useful but just to go over it really quickly again i think that's going to be useful when we are initiating this class we have underscore x this one here and this is the variable we actually storing values in right now that is 10 but it really doesn't matter what it is once we have that we are running this line here and this one turns x into a property and this property has a getter a setter and a deleter and those are referring to this getter here this setter here and this deleter and now from outside of the class whenever we are accessing this x here we are running either of these methods and these methods then influence self.x which is where we actually store the value so this one here and that way when we are accessing changing or deleting a value we can run whatever code we want in there and i hope this is making sense this is again something slightly more advanced that you probably want to practice on your own time but what python developers also realized is that this isn't exactly an elegant way of writing all of this which is why this property works really well as a decorator and let me copy the entire class to implement this as a decorator and let me comment out all of this and basically how you would implement all of this as a decorator for the getter you would simply add the add property decorator although then the getter you would rename to the name of the property which in my case is this one here so i just want this to be x now for the setter i want to get another decorator and this one you would call with the name of the property in my case again x and now you would add dot setter and then the actual method would also be called x or the name of the property and finally the same works for deleter so we need x dot deleter and now the name of the method should also be the name of the property so x in my case and now once we have that i can get rid of this property here and now this code does the same as this code up here meaning if i run out of this we get the exact same result except now it's a bit easier to read i suppose and this is something you are going to see reasonably often even as a beginner because running some code when you're just changing a value or even when you're just looking at the value can be really powerful so understanding this here or at least being able to replicate it by yourself is going to be something you do want to practice but again this is all very advanced so if you're just learning python and all of this is too much don't worry about it you are not going to use it for quite some time and it would be much more valuable for you to understand classes and functions by themselves so don't stress yourself if this is too much but anyway this is all you need to know to get started with decorators and i hope that was helpful
Info
Channel: Clear Code
Views: 25,195
Rating: undefined out of 5
Keywords:
Id: nVdF7QT-Ggg
Channel Id: undefined
Length: 42min 30sec (2550 seconds)
Published: Sat May 07 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.