The Mighty Dot - Customize Attribute Access with Descriptors
Whenever you use a dot in Python you access an attribute. While this seems a very simple operation, behind the scenes many things can happen. This tutorial looks into this mechanism that is regulated by descriptors. You will learn how a descriptor works and what kind of problems it can help to solve. Python properties are based on descriptors and solve one type of problems. Descriptors are more general, allow more use cases, and are more re-usable. Descriptors are an advanced topic. But once mastered, they provide a powerful tool to hide potentially complex behavior behind a simple dot.
In this tutorial you will:
- Learn how to use Python's descriptors to add new functionality to attribute access
- Acquired solid background knowledge on how descriptors work
- Work with practical examples for applying descriptors
- Learn when to use a property or reach for a descriptor
- Get to know how popular Python libraries apply descriptors for tasks such as data structure access, REST-APIs, ORMs, and serialization
This session took place in track Python Language & Ecosystem and was classified suitable for advanced python by the speaker.
Transcript (auto)
Auto-generated from the recording utilizing Open-Source AI. Speaker labels (Speaker 1, Speaker 2) reflect diarization, not identity. Timestamps refer to the recording.
Speaker 1 [00:07]
Let's get started. Please download the materials just a small zip file You just unzip it and then you get a PDF and view Python files. That's what we need There's no other requirements I'm gonna use in Jupyter. If you like Jupyter you can use Jupyter. If you use any other tool use any other tool whatever you like. I Keep the VLCAP back this is this thing because people coming in but just a quick intro who I am so here I'm I'm a Python user since 1999. So, you know, it's a claim since the last millennium. I'm using Python since version 1.5 Haven't found a better programming yet And I do Python training since 2004. That was more than 20 years now if I calculate it right I'm running Python Academy So I do Python training and I have quite a few colleagues that also help me to do Python training and these are trainer-led sessions like this in person or remote or hybrid and And we do this as open trainings in Leipzig, but joining remote doesn't really matter where it is, does it? But also a lot of in-house training for companies, institutions in Germany and all over Europe. I'm also involved a little bit in other things. I still do some, from my background of hydrology, do some groundwater modeling. But I'm also an organizer of conferences. So the first years I was mainly organizing. The first two PyGons DEs I was sharing and also organizing a lot was here at EuroPython Monster. And then also I'm the chair of the Python Software Forbund. You might have seen me on the stage at the beginning. So we are a German nonprofit that helps to organize conferences just like this. And I'm also founder of the Python User Group in Leipzig. And I'm a PSF follower and maybe a few other things I didn't write down here. Good. Back to the link. I would like to ask a few questions of you about your Python experience so who has less than one year of Python experience please raise your hand only one person between one and three years three to five more than five okay so they're more experienced and so usually it's more like a equal distribution about this it depends how how big I make the bins here, but so now there's a lot of people more than five years and Pythons are more experienced. And that's correct because this is more marked as an experience tutorial, so it's about descriptors, not decorators, so it sounds very similar. Yes, I also do a tutorial about decorators, but they're a little bit different. So this is about one feature of Python that's been there for a while and I've been teaching you this for a while and people always tell me that's a more involved feature so please download this one so people still came in download this one unzip it and when you unzip it you will get a PDF and we will pretty much go through the PDF here this is write down what we do and I will pretty much stick to the PDF so if you get lost you can always go back with the PDF all the code is available as Python files because copying from PDF doesn't really work so there's something so and I will go slowly through it I don't throw big notebooks on you. We go step by step. So descriptors. Descriptors, the thing is called the mighty dot. So every time you use a dot, you invoke a descriptor potentially in the background some way. And we will look at what it is. And descriptors are used quite a bit, but usually don't see them. That's supposed to be this way. You want to hide things, in a good way in this case, behind this dot what happens. And people always ask me for real-world examples and I came up with one and Esco Alchemy is using descriptors heavily at least that's what they say on their website and I looked in the source code a little bit and I have example here so this is just the illustrate you don't have to understand if you don't know what Esco Alchemy is you don't need to understand anything here but this is a real-world example where they hide complex behavior on the background essentially this is an example where you make a small toy session with Esco Alchemy and a table here so if you know this and then when you access the dots if you do something there's a dot here and access a dot then a lot of complicated things happen at the background but that's the main idea here so they actually generate SQL queries or SQL statements or other other things in the background to trigger something and that's behind the dot and the nice thing is if you want to change something how things behave yes it's not user-facing because we just write the dot and you don't see that the changes and when you look at here generator thing now this one generates SQL and you have so this is just a teaser there are quite a few libraries out there that you just use descriptors and we want to look at a little bit how they how they work good if you would like to ask questions you can always raise your hand if it fits I try to squeeze the question in I might postpone the question to later if I say okay we're gonna cover this later but I try to always incorporate questions if possible. So the first thing I would like to start with is properties. So I will develop new notebooks for each topic. In the back can you still see it's a big enough? Yeah, so of course. Okay, so I saved the notebook. I changed the name of the notebook and called it properties should we look at properties who have you used properties must write it right properties who have you used properties before quite a few people so properties a property is application of a descriptor it's a specialized use case of a descriptor but it's good enough for many use for many applications actually so we look at the property first because it's a little bit simpler to understand and then we go to descriptors which is a generalization and we see how a descriptor works. And during the training we also look at the differences when to use a property, when you use descriptor, when it makes sense. But so if you're, so I'm doing this all as a notebook and I also will then in the end also we do an exercise and there will be solutions to the exercises and I will upload the solutions and these notebooks I develop here, just in case there's something new in there, to the same location where I just downloaded this stuff. So we have this, if you unzip the file, I created an environment here that the pixiv file is not involved in this thing, but if I change to the property directory, I get these files, so you should see these Python files, and I'm just loading the Python file. And I have this example of a square. So this is a pretty good example of this property. So the property is often called a computed attribute. A computed attribute. So from outside you just see an attribute, but when you access an attribute something happens. That's the whole gist of the whole tutorial. And the property offers this. So what I'm doing here, I have a square. Yeah, that's a rectangle that both sides all have the same length. And I make a class, square, and I provide the site as an argument, and then I come with a property. The property, it's a built-in, so in Jupyter, everything that's built-in becomes green, so that's why it's green here, but not bold. The keywords become bold and green. So it's a decorator, so decorator, I teach three hours decorators, it's a, what's a decorator? It's a function, it takes a function, returns a function, yeah, so. principle has been possible in the very early stages of Python. Why has it been possible? Because in Python everything is an object, so that's always the most important sentence. Everything is an object and therefore functions or methods, which is the same thing, are objects and so you can take an object and give back a new object. And that's what happens here. So this property that does something magic, we don't need to know exactly what's happening. Just take it as a recipe, you put a property and the purpose is you don't need to understand what's happening, something's happening. And this changes the behavior. You see, I have a method called area here, and this area just returns the side squared, which is the area of a rectangle, a square. You have this, and then I have another function with the same name area. This usually doesn't make sense. If you have two functions with the same name, the second one will overwrite the first one. But But here we put another decorator and this is a little bit strange because now this area is this area. So this area name appears here and now this area grows an attribute which is a setter. So a property per default only allows your read access, we will see this. But if you would like to regulate assigning, setting, writing access and deleting, then you can add this setter and deleter here. you see now if I if I set it I just don't save the area I save the square root of the area which is the side value yeah so the opposite operation to the power of two is the square root good and then also over at the leader you don't have to do this this would be the default behavior just to show you you can interface when somebody when this attribute is deleted so if somebody calls DLL, DL, at the operator and deletes the attribute, yeah? In Python that's not very often necessary. I find myself using DL very, very rarely. If you write in a certain style, probably you don't need it at all. But if somebody tries to delete it, then you can interfere and do something, and the default would be that you cannot delete it, but I do this anyway. Good. Everything clear? Good. now I can have a small test which I in this case I would just uncomment the test, I don't want to execute the test and I do the test by hand which is actually better. So now I instantiate this square and if you look at the square now you have the side which we just said which is and if I access the area I do get the area 25. Yeah, so what happens if you want to you can do some print debugging in this case that's okay here yeah so you just say print and you put some print in here and you see what's happening and you see when I access this area this method area is called that there's no parentheses the only thing here I get rid of the parentheses why is important. So in the beginning you can start out just with a static attribute, a number, a list, whatever, and later on you decide that that's not good, I need to do something when somebody else has an attribute, but now I would need to change my API. I have to say to the user now you have to put parentheses behind it to call it, and if you use this one you don't. So that helps you to keep your API stable, you don't have to change your API, and you're free to change the behavior without impacting the user yeah and if I now set this one I can say square and I can set my area and this would invoke let's say I want to have 36 then if I look at the square side it changed and you will see now it's 6 yeah 6 times 6 is 36 it changed because through the mathematics the integer changed into float if you want to keep the integers you have to do some inspection and convert it but I I think integers and floats usually intermix very well in Python, so it shouldn't be a big problem. But that's the main behavior of a property. If I want to delete it, so if I use del and I want to delete my square and my area, then this will be trapped by my del method up here. Deleted good, so this is properties. They can be very useful Computed attributes and you don't have to change your api if things change so now you see now. I have the name area three times there That's a little bit Unfortunate a little bit unfortunate Let's say this actually that this is a main usage of the square so I mix up the order a little bit here but what actually happens in the background since this is a decorator let's come up with this other solution that's actually the same solution but a different spelling, different writing. I have this girl without a decorator and actually you can do this and there might be use cases where this is useful. So this would be exactly the same here, this would be exactly the same behavior so I have three methods now I use different names I don't say area and there's no decorator these are just methods inside the class and they do exactly the same as we did before and then I invoke the decorator on this way you can always invoke a decorator like this so this is a function that takes not only one function but many functions and returns something new here yeah return something new and before we use the decorator syntax, now we use this syntax. This can be useful if you do this automatically for some reason, you don't have to generate code that puts a at sign at each, the sign at each method, you can just do something like this, that would work. Yeah, so you can you can keep the old method around and generate a new one if you like, in this case this area would now be an attribute here and you could use this one. And you see here the name has to has to be fget, fset and fdel and you have to give it a documentation and I just take the documentation from this method, the ThunderDoc gives you documentation. Question? Would an IDE get this documentation within the helping methods? So most IDEs when I set documentation it will not take dynamic documentations into account. That's a good question. For example VS Code. Yeah, I'm not sure if should be smart enough to recognize that it's exactly the same. Writing a property and the syntax with the add is doing exactly this. I'm not sure how smart those IDEs are, so I haven't actually looked at it, but hopefully they are there. So I would recommend using the syntax that's recommended. But nevertheless there might be use cases, like if you automatically want to decorate many functions, you want to keep the old functions around, you can generate these new ones with this syntax. So there might be use cases, because you don't want to repeat this by hand, because you have to put a decorator by hand, unless you write them. You can write the class decorator that decorates your methods, that's possible, but it's a bit involved, so it makes it more complex, and this would be easier. So there's always a solution, but if you're in doubt, use a simple solution if possible. It's getting complex by itself, you don't have to make the more complex one. Good, but it's possible to do this. So now the problem is, I have three unrelated methods here, three unrelated methods that fly around, but they're all actually one. So these three are one, but it's not really clear. Up here it's kind of clear, but you still have to look at the decorator and they have the same name, which is kind of strange. So I have a found-another solution, which I have here. The problem is it's not a standard solution. So I have this one, I call it a nested one, and this one does exactly the same here, but it's different. So I write my own decorator and I have only one area and now I nest these three things. So these the fget, the fset and the fdel are just the same we saw before but they are nested and then I use locals. Anyone knows what locals does? Locals gives you the local namespace. So in this case it gives me everything defined in here. So it gives me fget, fset and fdel and therefore it's important to name them just like this because if you look at this these are the names of the keyword arguments I need to supply. And if I do this now locals creates a new dictionary with all these entries in here and then I have a decorator on it and this decorator takes this area and returns a property but here it's you see now use a property syntax and I just say names and names is a function called this is a return of locals and the double star takes the dictionary apart as keyword argument so there's a few things involved here yeah so but this would make it automatic the only thing that's missing is a doc string and I just take the doc string of the function to put it in and do you see it that's just names it's just a normal dictionary so we can run it and then if you want you can put some prints in here and you see how things look like so this is not standard, so always the problem is not standard, if you do something you have to explain it, it might be a bit more elegant but it's not a standard way, it depends, elegant is always subjective, but it's a way of doing it. We do exactly the same, so let's make our, I call it S2 here to give it this name, a square, if I have to execute this one, so maybe I could even give it a different name, might be a good idea, square nested. And I call square nested here, nested and I give it this name and if I look at the side, the side is 25 and now if I look at the area it's bigger, so it should be this one. So this still works and if you want to know how it works you can now put a print in here and you see what the names is so I say print names and then you see the names and I just re-execute everything and you see now when I call it I get this dictionary and this is a namespace from this function that you get a dictionary with the fget the fset and the fdel and then I adding the doc string in here so you can automate these things if you want to you can solve any problem with another level of indirection and that's our level of indirection so if you like it it's it's nice if you don't just take it as an interesting side point. Good, this is properties and we will see in most places you will get away with properties, but if properties are not enough you can go back to descriptors. So the property is a special case descriptor or application of a descriptor. We will see you can create the same behavior with descriptors yourself by combining a few things and this is a very common one so if you don't have a setter this would be read-only so if I only implement if I only implement this one and not the other two then you cannot modify it yeah that's what this is the behavior when you can delete it if you want modification deletion in a different a different behavior of these then you can implement them this is one use case but if this descriptors you can have more use cases, I will show you a few applications. Good. Any questions so far? Everything clear? Yeah. Hi, thank you. So in your deletion method you now said basically it's not deleteable, but how would you actually delete it? How would you need to change the message such that you can delete it? Yeah you just do something, it depends because when it would be deleted but you could, deletion is always difficult because you don't want to delete the method and the method will still be there but you want to have some action so depends on the context of your application so deletion means this in my domain then I do something because often you would do something this like this if you delete a file in the background or delete some from the database or whatever you want to do. Okay So it's not about deleting the method. Yeah, it's about it's about something is if it makes sense if you say I delete something I'll delete this attribute something happens in my domain, but I like a side effect sort of yeah some side effect You think that makes sense? Okay. Thank you. Shouldn't be surprising for the user should be something that feeds feels natural in one way or the other Thank you. Good. Good. Let's continue So we make a new notebook and this would be now we start this descriptors. So descriptors It's a bit can be a bit complex. Therefore first Yeah So this is a bit longer here. So if you look at this, it's a few more a few more Lines and I would start with what's called a data descriptor. So it's I need to explain a little bit Until it makes sense. So I have a data descriptor and that looks like this. This is a class but this class is never used as a traditional class, this class is never used on its own it's only used as an attribute of other class we come to this so otherwise it's just a normal class and we have init here and we have a get and set so that looks kind of familiar but a bit different so if you have this dunder methods so these double underscore are called dunder and they are special methods they have a special meaning yeah so they're usually invoked by some certain kind of syntactic things like the dunder add would be the plus so if you use a plus operator the dunder add is used and there are many others maybe 150 or so of these special methods and they have a special fixed meaning you cannot change it so never invent a dunder name yourself you never know a new Python version might have this new dunder name and then your program might have some strange behavior so and we have a get and a set and this one doesn't do much It just to get is doing some printing just for informative purposes and then it returns the value This is a very common pattern So this thing stores some kind of value and then I return here just to return the value unchanged you see later Very often you want to do something with your value before you return it or whatever you want to do Yeah, and then setting and now here I'm doing something in setting I get the instance in the value I will see in a minute what the instances And then I try to uppercase the value, which only works for strings or for other objects that have a .upper method, have a upper method, yeah? So if it's a string, it will be uppercased. If it's anything else, it will just stay the way it is, yeah? And again, I can have the delete. I don't need a delete. We will see this delete is also very rare. So by all these cases, I haven't really found too many use cases of the delete. The delete is something that's not that common to minor thing in Python. I don't use delete a lot. There might be some other applications where it makes sense, but deletion, if you write everything in the function and you leave the function, everything goes out of scope, you don't need to delete anything most of the time. Deleting things out of a list or dictionary is a bit different, but very often you don't delete, you use a pop or other method to get them out, so deletion is not that common. Once we have this here, now I can actually write a class and I call this with descriptor just a class and now this the only thing is that's usually how I use a descriptor I make a class attribute and I do this descriptor instance here and that's the usage any other usage out of a class doesn't make sense we can of course you can make instance but it doesn't do anything useful most of the time and now if I make make instance yeah so make instance of my with descriptor so I do have to quickly and then if I access my attribute you will see now it trickles a descriptor this is a descriptor how this works and now you see the print and I get my zero which is not very exciting but I can go to the to this descriptor and now let's look what actually happens in the background so if if I do the dot then this little short line while I'm doing here happens so Python uses type to get the type of the class then you have your class yeah so of course every object knows it's class is a direct class and then inside you have a dict so this is a the dictionary that holds all the data all the attributes and this is a mapping proxy the read-only dictionary that holds everything in the class so this dict you can look at it but please don't you cannot modify it anyway but some other places you might these these underscore things you need to know what you're doing just to look at it usually shouldn't be a problem but modifying things you really need to know what to do sometimes it can be it you have to do it if you do something more sophisticated sometimes but most of time you don't want to do this you just use user other methods to get there but in the background Python is doing this so you go to some dictionary and then it goes to the attribute yeah it goes to the attribute and then it gets your attribute which is a data descriptor and since is a data descriptor it looks for the get method it looks for the get method, it even knows interactively, looks for the get method, you have this get method and it evokes a get method and now again it puts my instance and it puts the cause, which I need to get again because I haven't saved it, so that's what happening. What would be an appropriate type hint? I think, do you mean of the return type? The type in would be... It's an integer here. The type in would be an integer because it returns an integer, I think. And if anybody has other opinion, I haven't even thought about it, but usually it would be just whatever it returns should be the type in. Because the method itself doesn't have, it's a method that you invoke, it's just a little bit different syntax, but you get back, it should be the type, whatever you want to return. I think, yeah? That's a good hint for the type hint. Okay, good. So we go through all this chess, so this is just some theoretical knowledge. This is supposed to be in the background, you don't have to look it up. You don't have to do this all the time, but you can work with it. So this is called the data descriptor because all these methods are present and there's Another one and this one is a Non data descriptor Non data descriptor and this looks different because it only has a get so if it only has a get I'm not there yet, I'm too fast, so forget this one. So because we need to assign to our attribute, forgotten, we need to assign to our attribute, so if I assign a 10 to our attribute then you see it this will be called and if you look at our attribute it is 10 and you get this output. If I assign a string, yeah, if I assign a string and if I look at my attribute now it's uppercased, so I do something. It's not very useful but you see something happens now when I assign I can do something and if you see instead of uppercasing we could do some checking. Check if the value is whatever we want and if not I raise an exception or do something else. That could be possible. So you can intercept this assignment process and you can do something. SQLAlchemy is doing this, it intercepts it and generates it. There's a question. Generates SQL statement so you can... There's another question. I have half an hour already. One question for the specific example that you showed here. Would it make a difference if you also use the property and setter approach for the descriptor? Would that also work? I think a lot of these things you can achieve as a property. So whenever you can use it. Now just try to show you the properties using this for you in the background. The property gives you decorators that's doing this what I did here by hand. But there are use cases a property cannot cover. Yeah, no, I mean actually in the descriptor class itself where you define the set and the get methods Could you I mean what they would the example work this exact same way if in the descriptor class you use property? I don't think so. You need to get and set as Undecorated that would that would mess up everything you will get very strange errors. I'm pretty sure so It's always I always need to try it out what happens and then usually reason afterwards because it's really complex a lot of things But I don't think it's gonna work. I'm pretty sure yeah another question What if you make another instance of whose descriptor is the attributes the same as the other instance or not? That's good Everything is a class attribute That's a good question We come to this so everything stored in the class and if you if you want to store the instance you have to go Through some more hoops have some examples about it. So the descriptor always works for the class So, if you want to share something between instances, the descriptor is good. If you don't, you have to do something else. We come to this. Yeah. Good. So, now we have this one. And now, I come to the, I was too fast. I come to the non-data descriptor, yeah, non-data descriptor. I think one is called overriding, non-overriding. They're different terminologies. So, if you, anybody read Fluent Python? Yeah. Yeah, so at the fluent Python for Luciano Ramallo, and he uses different terminology for this because it is not very, but this is the official one from the documentation, so I use it here somewhere in the PDF, I mentioned the other terminology he's using. So this would be a bit different. So if I have this non-DataDescriptor, so there's no setter, only a getter, and the getter for some reason, it's a 10 here, and now I make a class, I make a class called add10, which I write here and I have my attribute with a great name attribute and I make a non data descriptor and I put it in and then I have my add10 and then I make an instance, and if I access it, I have an attribute, yeah, and if I look inside, if I look inside this, the dict, so the instance also has a dict, which is an attribute of the instance, you see this instance doesn't have any dict, usually when you make an instance and you say dot, Python looks in this dictionary and looks for the value, nothing there, so it goes to the class. That's standard behavior. So first this dict first and then it goes to the dict of the class and since it's a descriptor it invokes this complicated mechanism to get there. That's what happens but if I have my class from before, if you look my class from before this one, the dict is empty, nothing in there. But I use the attributes and usually you get the attributes from the instance, this would be the instance attributes. Yeah, but now it didn't is not there it goes to the class because the descriptor works in in the class But if I go here now and now assign something to my attribute since I didn't overwrite I Didn't overwrite You see I get back the hundred because now I Didn't show the one before but it's ten to it If you look in the dict when I assign to it, it doesn't assign to a class that science is dictionary Yeah, so the reading goes instance first and then to the class, when I write it only goes to the instance, it doesn't go any further, so it stores at some different place. Yeah, so you can have the same attribute name twice in this case. Do you want this? Depends, so you have to know what you want, there can be use cases for this, yeah, there can be use cases, but there's always, if Python looks for name, it looks in different dictionaries in different places. Python scoping is the same thing, locals, globals, built-ins, these are dictionaries you're searching in this order. If you go to the method resolution order, same thing, you go to this thing. It's a resolution order, it's not a dictionary, but it's always Python looks for names in a certain order. And first you go to the instance, and if it's not an instance, you go to the class. And then you go to the parent class. Well, that's what happens here, and this is something you need to understand, this one. Once you understand, then you see, oh yeah, this gives me some leeway. If you look at some use cases, what you can do with them. Might not be clear right now, but I have some use cases where I combine these things. Yeah, good. One small side note. Functions are non-data descriptors. So, is this important? usually Not really, but it's interesting to know but then you know how method binding works So if I have if I have a normal function if I have a normal function Yeah, it doesn't do anything. It's just there and if say function You see this has a get method which is indication that this is a descriptor It only has a get it doesn't have a set that's why it's a non data descriptor So, if I have a class, if I have my class here, and inside my class I usually, this is, I call it a method, but it's still a function, it's still a function, and now I can make an instance of my class, make instance of my class, and of course, if I look at my class, this method is just a function, that's what it says. So anybody knows Python 2? In Python 2 it's a method and it wasn't called function, it was a different name. But since Python 3, so for quite a while, it's called a function. So it is just a normal function and you can look at a function. And if you can also say type, if you look at the type, this is the same thing. If you say my class and you go to the class and you look at the method, it gives you the name function. But if I say type of the instance, type of the instance dot method then you see now it says method and if I if I just get this representation of my method then you see it says a bound method. But what does a binding means when I invoke a method on the class I need to supply so if I can invoke this method it doesn't do anything but to able to invoke I just don't do anything if I try the same thing on the class this doesn't work if I use my class and my method and I put the parentheses what happens and it complains about something with a self missing requirement self what do I put in here. What self? The instance is a self. Yeah, so that's how it works. So whenever you use them, this bound method actually has this self used up already, so to speak. The partial function application, everyone knows, would do the same thing. So that's how it works. So when you call the method on the instance, this happens essentially, plus the other arguments you potentially supply. Yeah, that's what happens. And to get there, to get this behavior, the method is a non- data descriptor. So is it important? It's good party knowledge to talk about it. I'm not sure of the other thing, but why not, you know, we get some background here. So and now this the same thing, the same thing happens, so I go to my type and I do the same thing again as before. I go to my dict, yeah, I go to my dict, I I throw in the name of the attribute, which is method, I say dot get, get a get, and I do this again, I put in the instance and the type, which is the type of the instance. So that's a bit cumbersome, isn't it? So if you just say dot, and that's much better, that's what happens. So now you know how this method binding works. Is this important? If you do some metaprogramming, you write programs as modified programs, this might be useful because you might get a very strange error message somewhere and you know oh yeah I do this. Actually in the types module there are some a method called method type which is doing this binding for me so you can convert the function into a method type on the fly. That's possible. There are other ways of doing it to work with it. Good. That's the theory behind. Now let's see what we can do with it. So we know we can use properties is much easier but you can also use descriptors to work with descriptors. So descriptors work in the class and for this one I have what's called the class storage. Again comment out the example here. So now I have this descriptor called class storage and now I do a little bit different so I'm actually setting something otherwise before I said zero just you couldn't change it. So I have a default value which is none, but I can also supply a value. We will see in a minute where this goes. And then I have this get an instance here called instance and owner. That's usually some people use it. I use the same name here. We will see in a minute what it means. And here I just return the value. The descriptor doesn't do anything useful. You don't need a descriptor for this. But maybe later you want to change it. So once you have a descriptor in place, then later you can modify and do something here. But in this case I don't do anything useful. So let's go here and I just take my class here and I'm lazy now typing so much. And this one doesn't invert syntax. Yeah, just my commenting was triggered. So I make my class which uses this attribute just as in before now I'm do this and now I make two instances I make two instances here and again that's bit faster than typing I make two instances and then we look at the instance and I can work an instance have my first instance store one and store two and I can now assign the attribute is since I have this it's 10 where does the 10 come from yeah because I put a 10 here the 10 is from there so the 10 is a default value so it didn't assign anything the default value 10 jumps in yeah and now I have this this attribute and if I look at store 2 store 2 it also has this attribute 10, and now if I change this one, store 1, and I modify my attribute, and I say I want 100, of course if I look at store 1, the attribute is 100, store 1 attribute is 100, and if I look at store 2, what happens at store 2? If I ask like this, what should happen? It's also 100. So So a descriptor works in the class, was a question, so now these instances share one value, do you want this? Maybe, most cases probably not, but if you want it then that's what you have here and you could save, like if all the instances need to have the same ones, you don't want to put this the thing, the copy of each in each dictionary of each instance of many instances, you can share them from a class, that's usually one benefit of the class method. If it's immutable is no problem, but now I can modify it, I can take it away. So I didn't modify it, I just deleted the 10 and put it 100 there. So I changed the value behind attribute and now all instances have a different value. It doesn't matter what instance you use, you have this ghost effect on all of them. There can be use cases maybe, if you want it, but that's a standard behavior, that's the standard behavior of descriptors. If you don't want this there are two methods to do this. The first one uses a weak key dictionary which I have from this book, somewhere I must have referenced the book, I think it's called Efficient Python or something and I wasn't aware of a weak key dictionary before but it was an interesting thing. So let's look at the solution with a a wiki dictionary. So in Python when you create an object you create a reference. So if you don't know this, I can say this getRefCount and I can say give me the ref count of something. You see this is immortal object so it's not a good idea so the ref count of any object of any object let's make it a bigger number yeah and I can say sys get ref count you get the ref count at this get ref count of an object you get the reference count of this object a so this is very high this is a 4 if you do this in a normal Python prompt you get a two, that I get a four is in some artifact from the Jupyter Notebook. The Jupyter Notebook keeps a protocol that happens and then ref counting in the Jupyter Notebook doesn't work. You get always very strange numbers and if they keep executing things here, all of a sudden the number will go up. So it shouldn't be a four, it should be a two, if you do it in a normal REPL in Python. Why two? Because when you hand it in you create a, so this ref count measures itself, so to speak. The minimum reference I ever achieved was 2. Never get less. That's a little bit tricky. So every object has a reference counter. And this is a big problem, because as long as the reference counter is greater than zero, this object will be alive. Whenever you create a reference, you keep the object alive. You can use del as much as you want, you're never gonna del this. Only when a reference counter goes to zero, the object will be garbage collected. So now we need objects that are internal Objects that you don't want to keep alive if something else Because it's just an internal bookkeeping thing. They have a test therefore you have a weak weak The graph module and this allows you to create Objects that don't have a reference count There are quite a few things to it. That's not so easy. It doesn't work for everything You have to look through but now since we want to do some bookkeeping internally for something we can use this weak key dictionary. So if I put something in a dictionary it will be in a dictionary. If I delete this object it will disappear from the dictionary immediately. Yeah, that's the whole thing. It's a bit tricky but it can be used. That's what I'm doing here. So I have import this weak key dictionary and now I use a hidden. I have a dictionary and just think of it just a normal dictionary. The only thing it doesn't keep a reference. Everything else should be the same and then I get it I don't get it from here I just go to the dictionary and I go to the instance and I try to get it here so now the instance of a class serves as a key in a dictionary this works so instance per default are hashable you come to see some small problems here but for now it works so instance are hashable yeah so you can always try hashable either you put Dictionary you'll use hash when you see if it's hashable or not and that's that's what you need So instead of storing it everything in here now. I have a dictionary and I store I use the instance itself as a key in a dictionary to store everything so That's I didn't invent it I just found it somewhere and that's what it might be interesting and now when I assign it It's just this this hidden just behaves like a normal dictionary the same syntax here and they get means I get either instance if the instance if the key is missing I get a default so every dictionary person has a get method which is the same thing as try X access it and then accept give give me back default that what get is essentially doing that's what I'm using here so and if I execute this one and I go to do the same thing again so I make a class and I make two instances so I don't want to type so much so here I make a class into instances and I have to fix indentation here yeah didn't I execute the cell did I forgot something indentation is wrong No, store name is missing. Name store, what did it do? Never do live coding? What? Ah, they shouldn't be indented, yeah, thank you. Yeah, indentation thing. Good, so obviously that's in the class and let's make instances. Thank you very much. So now I have this store once and if I look at this now I can do the same game as before and I have my attribute here and you see that the attribute 1 is a 10 and if I have my store 2 the Attribute 1 is a 10. And if I change one of them, Yeah, then store 1 has changed. Store 1, attribute 1 changed. And if I look at store 2, Attribute 1, it hasn't. Yeah? Because now everything is stored in a dictionary and you work with a dictionary. This is a very common thing, very often in Python is called wrapping, you have a class that sits in front of a class and stores the value internally somehow. It's kind of similar to this case here, so this descriptor has a dictionary and for all instances that are created it puts it into the dictionary and then you can get it out of the dictionary. The thing is, if you do this and you delete the instance, you will keep the instance alive in a normal dictionary, because you have a reference here, if the instance is deleted it will disappear from the dictionary. That's the whole thing why you use a weak key, otherwise you have a built-in memory leak. So you have a memory leak once you build it in. So this is a nice but, a big but, that has a problem because you can break it. And I have an example here how to break it, because week key dictionary break yeah and the thing is if you either have this hash implemented the race not implemented or the equal so if you look at how if you look at the documentation about hash well this is a very long thing it's not so easy but you can you make your instances non hashable different ways intended or unintended so there's no guarantee that it works for everything so if you give this to somebody and somebody makes another instance it works but if somebody do is either of these methods here that's in this way then the thing is not hashable anymore and my nice approach just explodes nothing works anymore yeah so if I do this now I can keep this in here because you see if I if I do this I get I get a not implemented error because it's the hashing is called, so my whole, this is intended exception now, this doesn't work, because if you want to look into this hidden thing, you cannot put the instance in as a key, because it's not hashable anymore. Yeah, that's a problem, that's a small problem. Good, so I have my own solution, which actually I had before and I call this instance storage yeah and my own solution is a bit slightly different so I use the uuid module which gives you some use so instead of using the instance as a key I store everything in the descriptor in the instance sorry as an attribute but I don't want to make the attribute accessible from outside. So in Python there's no such thing as making things private, but you can make it very very clear, very clear, that it's not supposed to be used and that's what I'm doing here. Yeah, so using two underscores is does name mangling if you know this, so it puts a class name with an underscore in front and something, so you can still access it if you like, but it's a big red capital letter thing, don't do it. Yeah, so that's what I'm doing here. So In Python you will say we are all consenting ourselves. You can do always do something very stupid if you want But there's big red things that don't do it If you write a test and you want to test your own thing It might be fine But if you use a library then I think there's no use case that you do it You work against the library if you do something like this. So that's what I'm doing here I have a hidden name. I create a name and this name will be the attribute name And I use every time I use the same name for instances But this is a very very long string very random string. Yeah, so you might be different ways It should be long enough now they use your MAC address and the time and what kind of stuff and that ought to should be and Then you can do this and then I save everything here and then I go to the instance and this would be instance dot name So this is the instance dot name but programmed so get attribute this means this object dot this name and if it doesn't exist the default yeah so it's a try except actually it's in there and then as they said I say instance dot name equals instance dot name yeah okay let's do this step by step again here so I go through now I have this one and I create my class and two instances and I hopefully I can do this and then the indentation is correct yeah so I have my instances and now if I can look in my instances and I can look inside I can say store store one yeah and if I do this I get the attribute but if I really want to I can say store one dot double underscore and I should get this long name, so I don't know what the name is, it doesn't show me the name, yeah, store, I have to put the class name, class name, how do I get this thing, yeah, let's look in the dictionary, we look dictionary, then we have it, we don't need to guess, it's just a dictionary, so you look in the dictionary, the dictionary is empty right now, yeah, because I'm in the class, but now I can actually set the value, I set the value, the attribute value and I go to the setting and say okay 100 and then if I look into the store 1 and I access the attribute I do get the 100 and now if I look at the dictionary then you see I have this very strange name inside so I have this attribute now that exists but this is very obvious it's not for human consumption yeah so I store this in the instance and I access it from there but it's not designed for direct access so you can break it if you access it directly you can do you can I can modify the dictionary there's no protection whatsoever but this big red thing don't do it yeah so that's that's very very common in Python if you do a lot of meta programming you can always break Python there are some programs that people say one equals equals two is true so and that doesn't take much you just Take c types and you modify the reference of one and two. So you can always break things. But if you want to do something Correct, that's going to work. So that's my solution. I don't know if it's a good solution, but it works. So you can use instance storage to store something in the class. So, again, if properties are good enough for you as Properties, but if you want to know more, this is how you can Do the descriptors. Good. Any questions? Everything clear? Let's do some use cases, because I have some use cases. Thank you, so we've seen a lot of sample applications now and we're going to use cases. I'm curious, do you have a definition of what a descriptor actually is? Like if we had to give a definition to it, how would you describe it? It's a regulated attribute access I would say as one that's what it's not that it's not that it's like mathematician has this long definition no it's this this is would be my definition you you can intercept attribute access reading writing deleting attribute access okay so that's what it was yeah perfect thanks I have one question. I think most of the things that you're doing here you could also do with the danda get attribute method, right? Yes, the danda get attribute is very strong. So the danda get attribute has the highest precedence. It will be used always first and you have to be very careful to not get into recursive stuff, because it will intercept all this get attribute things. It would work for the get, but it wouldn't work for the set, because we also can intercept setting, and very often it's a combination of getting and setting, what you implement where and how to do it. We will see some use cases, you can implement both of them, one of them, and then you can have different use cases. Okay, I guess my question was, what is the reason why you would prefer one over the other I assume from what I see that it is using a descriptors like more self-contained that you have all yeah that has to do with the advantage of you a descriptor you can reuse a descriptor different places you write a rescriptor here and can use it over the place if you put it in your class you only have in your class and if you want to use it you would need inherit from the class but you don't mind inherit from some random class from somewhere else that's usually not good so I'll show you a lot of things and you might achieve the same thing for special purpose with other totally other methods somehow, but the descriptor can be reused over your code base and you have more possibilities for things. If you don't need them and you think it's a complex don't use the descriptor. I just show you what it's all about and then you can decide yourself if you want it or not and there are usually two or three methods doing it, but there should be one obvious way to do it. What's obvious? Subjective. One more question. If we go back to the class data descriptor in a data descriptor.py file. So we use this approach. And many times when I'm looking for, I mean, when I'm browsing through source code, I just want to see the help. I mean, I want to see the description of the class in some code base. and I use the help function, the built-in help function of Python. And when I use help for this class, data descriptor, I get four times the print data descriptor, dunder, get, dunder, and this happens four times. Even though we only defined this dunder get method one time, but it gets printed four times. Why does it happen? I don't know what gets printed, so this in Jupyter, I use an iPython, I use a question mark, should be the same thing. No, I'm talking about the instance. Ah, the instance, okay. So first we instantiate the class and then we use the help function to see and yeah, exactly. You see, why does it get printed four times? Yeah, because it's somehow when I access it. So this help is doing this head Inspecting it and every time it inspecting it it goes through my getter It invokes my getter every time so now we have to look at help what help is doing but help needs to look and Creates all these and for this one it has to inspect it and every time it Inspects it it creates it So the dots is also if you ever use the built-in function get attribute is the same thing as a dot should So if I use get attribute you should have this so that I have this if I have my W here. Yeah, and I say W attribute you had this you have this you should be the same thing if they get attributes get utter built-in W and then you put it as a string and that's probably what the help function is doing and it's just invokes the The descriptor so this is the artifact because I put the print in so in your real code you wouldn't put a print anywhere It's just to show you something happens Yeah, okay. Thank you So if you I would need to look at the help for this but it's probably inspecting it multiple times and every time it goes through this descriptor Good so you can always experiment and look at how this works, so let's look at some use cases So, I've picked out some use cases, these are just examples of course you can hopefully adjust them to your own use cases that are the same or similar kind of task. So, I say load, check, checked, so this is type checking, of course in Python you have type hints now, but here this is just an example, I could do my own type checking if I wanted to and I could extend it in many, many ways and this would be a run time thing, Python type hints are usually at compile time when you use a tool like mypy, but here I wrote my own thing. So it is a bit more complex. So I have my, I use my uuid approach here because I store things in instance so it incorporates what we just saw before. And then I have my init, and here I can provide a checker. So a checker is supposed to be callable. So usually you say a function, but when you say function Python most of the time you mean callable. Objects that can be used just like a function, but they're not actually type function. So let's call the call of it So I have a checker and if there's a checker, I just call the checker So like like let's say the checker is int. Yeah And then I would call int and if I call int on a string int it would work if I call int on Something that doesn't then you get exception. So this would be pretty much this would be an example of a checker Of course, you can write your own function. So int is not a function. It's a class but it's a callable So and then this would be the check I try to convert this object to this data type I want and if it doesn't work I get exception. So that's that's the thing here So and then I have I store my checker I store my default and I do my hidden name business here Yeah, and then like this the same thing the getting is just what we had before nothing new So if you wanted to I could have inherit from the other you can inherit descriptors. Yeah, but But I just write it here for to make it easier Did you get us the same and then the set would also if the if the checker exists So if this is if so, it has to be call of adults none or callable So if it's something else, but you get a different exception, of course So if it's if it exists, I call it here and it just do the checking Yeah You can do anything what I want and this one is supposed to raise an exception if it's something is there that you don't like And then you do the etcher So this this line this line is wet before the only thing new is applying the checker on the setting case So now I have my own function is int wouldn't be even necessary I could use indirectly but I want to have a nicer error message so and I say if this is not an instance of int so if you do type typing in Python if you do it at all most of time you want to have is instance yeah type is to very often to narrow if instance would also check for the parents I'm sure if it is instance file not found error will be instance of exception so if it's then all the exceptions if these instance of exception you there would be check all of them if you just check check one check the type that might be to narrow so I check for this instance and I raise an exception so this would be my business logic so this is the difference that different level of programs in one program so when you program very often you have different heads when you write a function you develop a library and when you use a function you use a library so Very small level though. This always I think and this is something you write once and then this is the next level This functions will be changed more often though. This descriptor will be there once it doesn't change and then for every use case You write a different Checking function. Yeah, it's just something you can do and then you apply this checking function to your class And you see now I use my checking function is int and 4 to 10 that works and then I just default to 12.5 without any checking as even those call check it doesn't do any checking because I don't supply any checking callable and then I try to do something like this it doesn't work that's why I put it into try accept to show you this wouldn't work because now I use my function here and I get a default of 12.5 and if I do this it jumps into in here in here this is a setting yeah so this equal sign this equal sign invokes this setting and since now I supplied a check on the beginning and this is a class this this here yeah this checkers int is set and then it goes through and then it tries to convert the 12.5 into an int which doesn't work if it was 12.5 is not the instance, it would work but it 12.5 is not the instance of int, so it says it's instance 12.5 on int and this is not the case and since it's false I raise an exception, that's what it's doing. Good, so enough if I run this one then you see now I can execute the whole thing, it cannot be set and if I work with this later on you also it's impossible you cannot set it because you have this checker in place. So three levels of code, this one you could use from a library and then you would write your own checker or checkers and then you use it somewhere else. And this is not restricted to type checking, you can do any kind of checking. So you say okay there has to be a value between 0 and 100 or no this test runs so long as Friday afternoon I don't run this test now or something like this. So you can do anything you want yeah this is totally up to you and you can inject it there and you can do it you see it using writing this class is a bit work yeah but later on if you can you reuse it if you do things only once it might not be worth it to write a descriptor but I say but I have this here and here and here and here pretty much all the same and don't duplicate code you can always write a helper function but maybe a descriptor is nicer and then you have only this check on then everything is just when you assign it happens and you can you can do this good this would be a use case so how much time do we have 20 minutes yeah so let's quickly go through now I have some section about them in the PDF and then to use things and not to use things then use descriptors and properties so then Properties when the property does a trick is a property that it's easier But sometimes it doesn't work. I will show you we have a few more best practices or use cases Which we can we can see which we cannot do with properties. So The property you have to take explicitly in all of these places there Yeah, and you cannot use properties across different classes the same property if you want to use a different class you can inherit from it, but if those classes are not related you don't want to create relationships between classes just to reuse the property, that's usually not good, because you have a class diagram all the classes are related for no good reason, so you don't want to do this usually. And therefore if you have this use case and descriptor and there are a few things, sometimes you might have a different behavior, so the standard behavior is you only have a get and you can intercept the setting and deleting. With descriptors you can get different behavior. We'll see in a minute. Yeah, good. So try to use a property if it doesn't work or if you say okay I have the same property here and here and here and these class aren't related and I need it in my whole code base, then you say okay I put a bit more work and write a descriptor but in the end it's less code because I don't have to duplicate it. Of course you can always use it, write a function that you import and then you use a function in a property that will also work to some degrees, it's not a clear cut, but the descriptor is an official way of doing things. Good. I have a few more practical tips. One of them is called the same name trick, the same name trick. So everything I show you now is just a combination of things we did here, but maybe with some ideas what you can do with it. so the same name trick and I call this thing percent yeah I think love the same name trick so this is from Luciano Romano's book so let's say he calls the same name trick so I just use a different example here and I have a percent you see now here I have this descriptor and this is new you have this thunder set name if you Python versions before I had to go to some hoops when you have to say okay this attribute has to be same as a string and so on now it's easier you have a set name so you can just use set attribute name here we will come to this in a second what it means and then over you have a setter there's no getter this is not possible with the property because a property by default all that starts with a get here I drop the get but only have the set different thing yeah so and then when I said it I do is I'm checking I say my value is supposed to be between 0 and 100 if not there is exception otherwise here I need to say dict so I always preach don't do this here I have to do it so usually you say instance name equals and here I want to write it directly in the dictionary because if I don't do this I go through my descriptor protocol which I don't want to so I write it direct to the dictionary in this case yeah so most of the time you don't want to do this here I think this is a way the way to go so this is something might take you a while to experimenting how things work but I write directly as a dictionary and now I have a class with percent and if I do this yeah now I have this attribute percent and I have the same name check, you have to use the same name here and there to make this work and now if I have my call p here with percent percenting. So I do this class and I have to give give it a name and if I do 34 that's totally fine if you look at the attribute percent is 34 if I try to set this to a value outside of this range yeah so some people over there I give 110% so this is not allowed here then you get this exception. So how does it work? This obviously goes through the descriptor. If I try to access it I don't go to the descriptor because this is stored in dictionary. So if I do this, I look in the dictionary first, so I get the value from the dictionary, but when I set it I go to the descriptor which in turn... So there's no class storage involved here, everything's the instance but you have this checker when you before you put it in it's called the same name trick because you have to use the same name here and there yeah and before you had to use a string you have to put the string percent in here but this set name is used so it sets the name so I can use this attribute set name is attached to this thing and this is this is a string This is important to use the string percent. This is a bit involved. Take it as a recipe. I have a similar use case. I can look at it and modify a Few things. You don't call it percent. You call it something else. You have to use the same name. That's all. This is something you cannot do With a property because i only have the set but not the get Involved. Good. Yeah, so if you want to have a read-only, that's a problem, sorry. If you want to have a read-only, then you need to... My read-only, that's my read-only, yeah. you want to have a read-only attribute and then it's a problem if you just say okay I want to read it only then I only have a getter but I don't have a setter this doesn't work we'll see in a minute why if you want to do it correctly you have to implement the set but raise an attribute error because if you don't have the set we don't implement the set the set is just going to standard procedure and nothing happens. So if you want to prevent writing to an attribute, make it a read-only attribute, then you need to do this. This is something a property can do. If this would be the standard behavior of a property, if it don't implement the set the property would have its own set and just say okay you cannot do this. So this is just an example, again you can do this with property but here it would be a reusable and you can do research whole code base without inheritance, without re-implementing. Another question there? Just about the pattern, I think in other places I often see setting a function that is like prohibited from access to none, would that be possible here too? it's just a matter of choice. I didn't understand the question, which function you set to none? To not have here a dundaset raise attribute error, but dundaset equals none without the def. Yeah, it would be the same thing, then you try to call, but then you get an error message, none type object doesn't do this and this, which is not very helpful, and here it's about the exception, the exception is more descriptive, because I raise my own description, otherwise you get this very very famous exception none type object and you get ten thousands of questions that overflow about none type object is not able to do and this is what you want yeah okay thanks yeah it's about the the exception being more descriptive good that's one use case and quickly go through and then we have one this is caching so So you can also do caching, that's one way of doing things, so if you don't want to recompute things you can do caching. And again I use this set name, so because we use the same nametag thing and now I have the get and I check here if there's no instance provided I raise a value arrow and here the The first time I do some expensive calculation and I put the calculation in the dictionary of the instance. The very first time I go to an instance, there's nothing, I do a computation which is represented here by printing and the next time it will be in the dictionary. Let's do this. a class with cache and then I have my instance here and now if you look in the dictionary in the beginning it's empty, and then now I can access my attribute. As I can see if I access my attribute it's doing some expensive computation here and if I look at the dict again it will be in the dict and next time I access it it will take directly from the dict and so convince my expensive thing. You can invalidate the cache by deleting it out if you want to get more fancy yeah this would be one day. Of course Python has a decorator called LRU cache, which is pretty good for most of the things, this would be different in attribute access. Again, nothing new, we have seen all the bits and pieces before, writing a dictionary and this is a way of doing it and you can only compute it the first time, but you could also have a person in the background say, okay if it's older than, in addition you put a timestamp, it's older than a minute or two or five, then I do the computation anyway. Then you would need to delete it, here in this case you would need somebody to go there physically and removes it from the dictionary otherwise you wouldn't be able to do this. I would think through how to do this exactly what would be a good thing but it's certainly possible to do it but you would need to do some extra steps from outside. Good, so okay we have one more topic and then we have exercises so we have how much, when did it start? We start 15 past, so it's 15 minutes, yeah? Okay, so I have one more thing about special methods, I'm just checking because it's pretty big if I want to go so so special methods are these Dunder underscore Dunder methods and yeah so let's see I have have a I have a class that has a special methods called contains and contains is used when you use the in operator that in if contains this might go for some different but But this is the first thing it will call when I use in. And now I can make an instance. Let's do this again. Take this out here. Let's do this. And now I make an instance of my special. And I can look inside my special. And if I say something in my special, abc in special, then I see it says true, because it always returns true no matter what I'm doing. But you see the side effect, it prints got abc, now this in invokes this contains method. It contains method. When I assign to it, which I can, you shouldn't do it, when I assign to it, it still works. Because the in doesn't look in the instance, it goes to the class directly. So special methods go to the class directly. And now if you look in your special, you have this stored here. So the dict does have the contains, but this one is not used when you use in. If I call contains directly with a parentheses, it will give me an error message. Yeah? Or say now int object is not callable. Yeah? So you have to be careful. Those special methods don't go through this normal scheme instance first in class. They go to the class directly. If you invoke them with a syntax, if you invoke them with a name, then you will get. So because let's go back. So if I do this, I make the instance a new, you see, if I do this a new, this would be the same as saying special contains ABC, yeah? This would be the same thing, but once I assign here and have this one and now I do this again, now I get an exception, yeah? Because before the dictionary was empty, so do it again, special, the dict is empty, yeah, the dict is empty. So we always go to the dict, the dict is empty, so this one goes to the class and then it works. If I assign it, the dict is not empty anymore and now this one breaks. Yeah, we be careful how this works, so special methods are special and you can you can work with it. Good, I have one more thing with protecting it but that may be, it's a very long one and I have to do method binding and so maybe it's too much, because there's a lot of examples here, applications we can have and you can work with it. When does it end? How long do we have? 10 minutes, so 10 minutes, so that's not much time, because I also have an exercise, so usually I have a bit more time for this, but it's fine. So we have an exercise, let's quickly go to the exercise and then we have time for questions, so if you want to do the exercise, please do them, I will supply solutions to it. So this last thing I didn't manage, but it should be fine, it's pretty complex, so let's go through. So So the exercise here, you can write a descriptor that only allows positive numbers to be set. So you could use properties in some places, sometimes use a descriptor. This one we call for descriptor to exercise a little bit. And then when it returns, you round it to decimals and then prevent a deletion. That's easy. And then this one would be interesting. This would be a reusable descriptor. So you have, like if you're in Europe, every country has different VAT rate, and now we created a descriptor that we can use to, it takes the VAT rate, like Germany 19% and some other countries, most countries have more, and then when you access it, it actually gives you the net price which you put in and the gross price including VAT, and you make a class for each country and then when you access something it calculates this one in the background. That would be just a simple example, so usually we have half an hour for the exercise, so in 10 minutes it's just unrealistic to get through. Please try, I will supply the solution document where we have an example solution that goes through, but to go through the solution would take me another 10-15 minutes to explain this, we don't have this here, but hopefully you get the gist. You know take-home message is if you want to do something try properties first, if they work for you you're done, if they don't work analyze your problem and say why that doesn't properly not work because doesn't work because I need to distribute over many classes then you have to go to the descriptor or I have a use case where this getting doesn't help I only want to use the setting mechanism then I also need a descriptor. Yeah so that's pretty much a take-home message otherwise it can be pretty involved but you always can take these as a recipe and apply it to your problem as all the technical things are in there just a question what you want to achieve and of course probably you need to write some tests to see if it's doing what you want so of course this takes some experimenting if there's not a corner case I've forgotten somewhere good we still have five minutes any more questions about anything? Also from people online, so I don't see you online. Nothing on Slido. Okay, no Slido questions? Yes, first of all, thanks a lot. I think for someone who is not that experienced in Python was also nice to just get a better understanding of how it works under the hood. Now my question is, there are obviously libraries that already do some stuff that you showed, for example checking that a value is in between a given range, so Pydentic can do this. Would you actually recommend using custom approaches like this or would you recommend to use like libraries like Pydentic or I guess MyPy or something like this? The answers always depends. So if this one has advantage, it's built in Python, that's not going to change, the Python is not going to change. If you use Then you have the normal thing, you have a dependency. If you use pythantic anyway and you depend on it, it doesn't Matter. But if you just for a small Thing installed by pythantic, your dependency stacks grows Just to have something. So that's the thing. There's always different ways of doing it. It depends where you have to analyze your problem and then Make a list of advantages and disadvantages. That's part of python. That's a library. Pythantic can do this much better than i can do it. then use it, but Pathentic does something a different way, I want to do it, and I would need to change the way of Pathentic is doing it, maybe, so there's no clear-cut answer for all these things, and things change, so that might be correct now, then your requirements are changed, you want to do something more, and then the situation might be different, so there's no answer here. Thank you. Anyone else? Yeah. There's one. We still have four minutes, so I try not to go run over, because people want to have breaks. Yeah. Thank you for the presentation, first of all. I have one question, what confused me a bit is usually I try to think of class variables as variables that are shared. So there's only one. And there's instance variables that are not shared. And now you showed us a kind of complex mechanic to have class variables that are different for each instance. Do you see a danger if someone else reads my code and thinks it is a class variable and then in the end it's not really? Yeah, so it depends who our users are. So if the user is using a class and everything feels natural, it's fine. So it depends on the domain of the user. So I work a lot with scientists and engineers. They use programming as a tool, and they don't care about all these fancy details. They never will write a descriptor in their life, I'm pretty sure. And if it works naturally for the domain, it's fine. If you do something unexpected, it's not good. That's my rule of thumb. So if you overthink about why it doesn't understand, so you want to hide complexity in a good way. So you give the user the least surface to work with, and it works incorrectly, and you do all the complicated stuff in the background. If this is achieved, it's fine. If it gets complicated, the user not understandable, then you did it the wrong way. And of course it's not really clear, but now you still store it in the instance, but in a different way than the user did. It works directly, but a lot of users don't know about the dict and the dict in class and all these kind of things anyway. And now we change this behavior to check for values at the end. the user gets exceptions say your value is not correct and that's what counts and how you get there as long as it works for the user I think it's okay all right thank you there's another question there we still have two minutes thank you thanks for talk yeah it goes somewhat in the same direction as the previous question so I I think the descriptor framework is pretty powerful. I'm just wondering, conceptually, if you want to control the attribute access, really, on the instance level, now you showed these tricks with the VGraphs and these UIDs, why conceptually don't descriptors work on the instance level? Why can't I assign a descriptor to an instance attribute? Yeah, so because usually all the methods, you could do other tricks. You can assign different methods to different instances programmatically, also there are different ways. But this crypto protocol is used in Python anyway for a lot of things, and though you can use it, but you can achieve similar things. You can do a lot of metaprogramming, and you can have a class decorator that decorates all the methods. And then you can also, if you wanted to, after the init, do something after the init and then modify. There are a lot of things you can do. I don't think they are better, because the more you invent, the more you can do wrong, and descriptors are a part of Python internally, and that's well proven. It's not easy, but they should cover all the corner cases, so if I come up with some own smart solution, I find out this corner case and this corner case didn't cover, so it might come much later. So I think that's probably the reason to put all the methods in the class. You can put methods in instances, because you need to have a computable something. all the methods are in the class, and it's a good way to do it. There are certainly other ways, even the same thing, but this is something that's, there's a long explanation in the standard library, the standard documentation. Thank you. Okay, no time left, I think. Time is up, yeah. Okay, time is up. Thank you, Mike, for this extremely interesting tutorial and well once again thank the audience for all the questions and their participation and have a nice day