Aspect-oriented Programming - Diving deep into Decorators

Audience

This tutorial is for intermediate Python programmers who want to dive deeper. Solid working knowledge of functions and classes basics is required.

Format

The tutorial will be hands on. I will start with a blank Notebook for each topic and develop the content step-by-step. The participants are encouraged to type along. My typing speed is usually appropriate and allows participants to follow. The students will receive a comprehensive PDF with all course content as well Python source code files for all use cases and large code blocks I use. I will load these files in my Notebook. The students can do the same or open the files in their preferred editor or IDE.

I also explicitly ask for feedback if I am too fast or things are unclear. I encourage questions at any time. In fact, questions and my answers are often an important part of my teaching, making the learning experience much more lively and typically more useful.

So the participants will be active throughout the whole tutorial. There will be two exercises that each participant has to do on its own (or in breakout rooms if the tutorials should be remote) during the tutorial. We will look at the solutions during the tutorial. I also supply a solutions PDF after the tutorial.

Outline

  • Examples of using decorators
    • from the standard library
    • from third-party packages
  • Closures for decorators
  • Write a simple decorator
  • Best Practice
  • Use case: Caching
  • Use case: Logging
  • Parameterizing decorators
  • Chaining decorators
  • Callable instances instead of functions
  • Use case: Argument Checking
  • Use case: Registration
  • Class decorators
  • Wrap-up and questions

This session took place in track Programming & Software Engineering and was classified suitable for intermediate 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:03]

My name is Mike Muller and I will be your tutorial instructor for the next 90 minutes. I would like to introduce myself in a few seconds. So I've been a dedicated Python user since 1999, so I can always claim since the last millennium. Starting with version 1.5.2, we just discussed this here. So I do have a scientific background. We worked at a research institute and then in 2004 I gave my first training and those were the things developed. And since 2010 I spent most of my time on Python training. training, so I do quite a bit of these kind of things that I'm doing here. Good. Today we want to talk about aspect on the programming. I just would like to know a little bit about you very quickly. Who uses Python less than one year? Please raise your hand. One person. Good. Between one and three years? A few people. Between three and five? Okay. Most. More than five. Yeah. So kind of equal, more or less equal distribution. So the time of using Python is not the best measure to how good you are in Python, what you know. But you're here to learn something, you know, about some features of Python. So please download the material. I uploaded the QR code was wrong. My tool to create QR codes was in there. So I somehow copied the name twice. So but now this is a code. will be the PDF and the source code. So please use this bit.ly link there. I track that it should work. Expect you on the programming. Give you a little bit time, and then I would go quickly to the material you want to cover, since we have only one session, which is not much. So, and I'm gonna use a JupyterLab notebook. You don't have to. You can use whatever tool you like. You are encouraged to work along if you like to. If you don't like to, you just want to watch, that's also fine. I try not to be too fast, even though we don't have much time. So I have to speed up things. Everybody has the link? So I'll quickly go through. This is the material. So if you download, you will have this PDF with the material. And this is the content. So I'll make it a bit bigger here. So we're going to have two big sections, basic decorators, where I explain the basics so that you get to know how to write your own decorators. And then we go into a few more advanced topics like parameterization and using classes, writing decorators and all kinds of things you can do with decorators, yeah? So I do have examples for all of them, and also we do have exercises, but due to the kind of short time, we cannot do the exercises, it doesn't work. So you have to get the exercise as a homework. I do have a solution PDF, so I will add the solution PDF after the training, just the same link, just the same zip file, and if you download it again, then you will have the solutions, which are example solutions, yeah? So it's a – it's quite a bit of topic, but that's why we have to stop there, skip the exercises between yourself. So I do start with this – with a notebook. I always make a notebook, and the first one is the basics – basics, basic decorators. And we look at what decorators are, what they're good for. So who of you use decorators? You put this at sign in your code and it does something. Who of you used it? Most of the people. Who wrote or tried to write their own decorator? Quite a few people. So first, I want to give you some incentive why we do need decorators. So I mentioned I started with Python 1.5, and I think in 1.5 you could do very similar things than you can do with decorators which are a bit newer. So motivation, the motivation for decorator. So if you have a class, lowercase class, so by the way, is this big enough a font for the back of the room? Yeah. Okay. So if you have a class, and I define a class, then I can have something like a static method. So in Python, static methods are not really necessary if you don't want to. So I think you could delete static method from language, it still would work. language like Java, you need them to write functions, so a static method is just a function that resides inside the class. This wouldn't work because I forgot the self, but I don't want the self, and to do this, I can do something like this. I can say a static method and turn this function into a static method, which does nothing. It circumvents the binding. So usually when you have a method in the class, then you can call it sort instance, and you You don't use the self, but you have to put the self in here. So this is a class, and I can use this function, this function on the class, and it behaves like this. And I can do the same thing for the instance I have to make first. So I have to make instance first, not using the instance. So I make instance of my class, and this works. Notice that's the same. I if I don't have the static method if I have a method and the method I would write a self in here and then I do the same thing I return yeah so and this would be the link. Decorators. QR code. QR code is broken? That's a new QR code. That's a new one. Please cannot tell you, but it should be. Because I saw the mistake when I made. Yeah, it's a new one. So I deleted the old one. Decorators are a great means of injecting the motivation before they go to the basics are great means to inject code. That's why it's called expect-oriented. You have an aspect, some kind of what's called cross-cutting concern. And we come to some examples like logging or caching. And decorators are great means to inject this code into your program. Yeah? And this is typically called a spec-jointed programming to give it a fancy name. We can also say decorators are functions that take functions, return functions. So that doesn't sound much better, but that's essentially what happens. Good. So everybody has a link. So continue. So now we have this class, so to see the normal, so now if I go to the, and I have my instance, maybe that's not good flow. If I have this normal instance on my C, I can call my method, yeah? And when I have to redefine everything, so if I call my method, this works. If I do the same thing on the class, it doesn't work, because it's bound. And if I call it like this, I get an error message, and I have to supply the first argument. That's object-oriented programming. You need the self-argument. You have to write the self. So sometimes you want to have a static method, which is just a function that's located inside the class. It's located inside the class. just a normal function, but for organizational reasons, you want to have it in the class. So you see, it's very important that you know that's a static method because the thing behaves differently. So I didn't forget the self. I don't want to write the self. But this only becomes clear if I see this line. The problem is I can only write this line after the fact. I have to define the function first before I can write this line. Yeah? And that's kind of a problem here. It's kind of okay because the function is very short. If this function has 10, 20 lines, you might not see this line there and just wondering what it is. Yeah, this can be problematic. So and now there's something new. Instead of this, we use a decorator, so I call this class C2. And now I do the same thing, I achieve the same thing by adding a decorator here, and I use static method in front, which does exactly the same. So this would be equivalent to this line. So if I can delete this line, and now, if I do this, this would behave exactly the same. And if I make a new instance of my class, then my function would run on the class without a problem. Yeah? So that's the whole thing. That's what it's doing. static method decorator is just doing this line for you. The very important thing is that you do it in front. You can see it, and it's also nice, colorful, magenta color here, and you see something is happening. Something is different. And this is an important thing. This is some people call it syntactic sugar, but it's important, syntactic sugar, because this is a very important message that we convey with these things. Good. The same thing applies to class methods. I want to skip this here. This is exactly the same. Class methods are very often used in Python to have a kind of what's called the second constructor, if you want to call it. So class methods would be another application where we typically use decorators. Where you can find decorators in the wild, there are a lot of libraries that use decorators, sometimes extensively. Qlik is one, the command line tool program, which is pretty common. Django is using decorators a lot, like for who is allowed permissions and so on. Flask is using decorators. Science is using decorators for types, number, if anybody is in numerical programming. There are a lot of libraries that use decorators, and they are all for the same purpose, to modify functions in one way or the other. Okay, this is the motivation, and the important thing is that you think this one is just exactly the same as this line, and when you know this, you can explain a lot of things, what actually happens with decorators. Otherwise, it's too much magic. But in the end, it's just this thing that's going to happen. And if you do this, you can explain to yourself what happens. I would like to introduce another concept, the concept of a closure. Who knows what a closure is? A few people. So closure is a term from functional programming. And since we have a function, it takes a function, returns a function, this is some kind of stuff from functional programming. So, Python is a multi-paradigm programming language, and it supports a few functional features. It's not a really pure functional language, but it has functional features. And I give you an example. I have this outer function, inside the function, I can define a new function. So, I can define a new function, which I call inner, which takes an argument 2, and now I can say return argument 1 plus argument 2. And now I do this different thing. I say return this inner function, so I generate a new function, and now I can say inner 10 is outer 5, 5 or 10, if I say 10. And now I can have this thing, which is a function. And I can call this 4, and you see you get a 14. So this is actually calling the thing. So I generate a new function. If you look at a function, you see this function. This is a function with a strange name, with a strange name. And the outer function does nothing else than give me back an inner function. And, of course, I supply the 4 here, but somehow it remembers the 10. Usually, when you have a function, you get something in, and when the function is gone, everything is gone. All those variables inside a function are not available anymore. Yeah, there's scoping. There's a local scope of the function. When you call it, they are available. If the function is called, they are gone. But somehow, this inner function has access to this arg1 from the outer function. And how does it work? How does it work? And this works with this closure. So, you can say closure, and you see, if you look at the closure, this is a tuple, which you can see by this trailing comma here. One tuple has a comma there. You can access the first element, and then you can look at the cell content, and there you have your 10. So, this is a very common concept in functional programming, and Python uses this also. So, this outer function holds on these arguments because inner function needs this. So the minute you define it, it realizes, okay, this arc one from outside is used here. And this concept is used now for our purposes, writing a decorator. So now we know what decorators are good for. Now we know a little bit about closures, how closures work. And now we're going to write our own simple decorator. So I start with a decorator that doesn't really work, but it gives you sometimes it makes sense to do something that doesn't work and start to improve to make it work. So I write a function hello that takes a function and prints hello. So this is just a function. And now I can define, I can use my decorator, hello, and I can define another function. So we will use always a very simple function, add A and B. So we will define this function a lot today. And I return A plus B. So if you see this, now something strange happens. Something strange happens. When I apply this function, you see the hello. And I define this function. Something happens when I define the function. Why is this? So the explanation is as follows, because this is just doing the following. So this is always the explanation. You say add equals hello add. So that's what the decorator is doing. And now it becomes very clear that you just print add. That works. That works. The decorator does not. There's no error message. It works. When I call my function, which should give me a 7, I get this the most common error matches on stack overflow, non-type object doesn't do this and this. And now you have this problem, yeah, the non-type is not callable because I forgot something very important. I replaced my function with what? With the return value of hello, and what's the return value of hello? It's none. If you don't put a return, Python automatically returns none. So now I replace my function with none. This is probably not what you want. That's I think in all cases a mistake, I'm pretty sure. You can do it, but it doesn't make a lot of sense. So we have to write a better decorator, and to do this, we have to do something. So you see, although this happens at definition time, this could be a use case for decorators to do something at a definition time. The more common case is to do something at runtime, so you replace your function that your function is doing something different at a run time. There are use cases, I'll show you one at least, where you can do something at definition time. Two different use cases, but most of the time it's run time. So now I redefine my hello in a more sensible way. So I have a function, and then inside my function I have this function you can call whatever you like. Here I call it wrapper or whatever you want. And then I use this syntax. Who is familiar with the syntax and knows what it means? Half the people. So I will explain the syntax in a second. Because this is essential. And now I do my this is my aspector, the print hello. Here in this case, it's aspector. This is something I always want to add to functions. Print hello. That's a double. Thank you. Thank you. That's a typo. Yeah. Typing and talking. And then I return my function, and I use the same syntax, one arcs and WK arcs. So KW arcs stands for keyboard arcs, and arcs stands for positional arcs. And then I return my wrapper. And now I can do the same thing. I just can use this hello and apply the hello to my function A and B I define here. So you see, now nothing happens at definition time, but when I add my numbers, I get my result. And in addition, I get my task that is the aspect, in this case, printing hello. So you can do something more sensible, but this shows the concept of what you can do. And now if you look at a function add, it also has a different name. This is not nice. We will see later how to improve this. But now add is a different function, because this one here, remember, this is just exactly like here. This is just this cell. This happens. So if you look at this, this happens, and you always understand what's going on. Okay. Now, what's up is this args and keyword args, so you need to understand this concept to make sense of it. Otherwise, you can always use it as a recipe, but it's a good thing to understand what args and keyword args are. So you can define any kind of function, and you can use args. And I just say print args, and you see when I call this function, I can call this function without any arguments, and I do get an empty tuple. If I call this function with one argument, I get one, a tuple with one. If I call it with two, I get a tuple with two, and so on. So this star args is collecting all positional arguments. So this function takes an unspecified number of positional arguments, 0 to n. This is a very versatile function already. And this is the first one, the second star with a double star is for keyword args. And now it's a bit different. So if I call this function again, and you see now I do get an empty dictionary. And if I call this with an keyword argument, I get an empty dictionary with one element. And if I have it with two keyword arguments, it's two and so on. Yeah, something like this. Of Of course, very often you combine it. Can you make it a little bit slower? Yeah. Okay. Thanks. Okay. So, this is the one that catches all position arguments. And this is the one that catches all keyword arguments. And the position arguments will be packaged into a tuple because a tuple preserves a position. It's a sequence. keyword arguments go into a dictionary nowadays dictionaries also preserve the sequence because they have their order since python 3 6 but beforehand it was not the case yeah so in general dictionaries use a hash table they don't necessarily have order that they have order now something particular in python but still very often the order is not considered so now we can combine both yeah so now i just put both together and then you might guess if if i do this then i I can put them together, KW args. And now if I call my function without anything, I do get two empty structures. And if I call it with positional and keyword arguments in any kind of combination, yeah, so conventionally you don't put a space when you call it like this between the equal sign and the value, then you get these ones. So this function can be called as any kind of signature, so any kind of combination of positional and keyword arguments. And this is important because it's like a joke called this function can replace any other function in terms of signature. So it can replace any other function. If it does something sensible, I don't know, but you can replace it. And this is pretty powerful. And that's not much new because you know how to work with tuples, you know how to work with dictionaries. This is important. It is often the case you always come back to the same data structures and you don't have to learn yet another specialized data structure for something. You can use an old data structure, a tuple or a dictionary, and it's very worthwhile to learn all the methods of these data structures. So another way, you can also use a tuple to call a function. Let's make a list, yeah? So now I can say list, print list, and if I print this list, it prints the list like this. But I could write print list, square brackets, zero, comma, list, square brackets, one, comma, list, square brackets, two, and so on. So this would print it out. But lazy programmers are good programmers, don't type too much. You could also say, do this unpacking for me, this star unpacks. Yeah, this star unpacks. And here, it unpacks this list. And it doesn't matter if the list has three elements, or 30, or 300, it will feed them as single arguments to your print function. That would be useful. And this always works. And that's, these are the two concepts we use here to write our wrapper. You see here, that's a definition line. Here we use packing. So we pack all the arguments in containers. And here we do unpacking. we unpack all the arguments. Because here, you call the function. Exactly the same syntax, different context, exactly opposite meaning. It's like a light switch. If light is on, you turn it off. Each TV works like this, and two-year-olds can figure this out. It's not that difficult. That's behind here. So same syntax, different meaning, and it's just a container concept. You can also say the postal concept, you send just a package, and the postal office doesn't care what I put inside the package, as long as it's not the big or the heavy, and I pay my postage, they will transport it for me. Exactly the same happens here. Package or container, the container is the same thing. You just ship a container, and you can put anything inside. As long as it's not something illegal, you can put anything inside and can ship it. Yeah? That's totally fine, and the container doesn't care. And this is a concept we use here. If you understand this, all the rest is details. But let's look at the details. So a few things about best practices. So what we did so far, if I write a function at, so I don't need to copy, I don't need to type this. I have my function here, and I forgot the most important thing in my function. What's the most important thing in a function? The docstring. The docstring, exactly. Good. The docstring. I forgot a docstring. So you can use single or double, triple quoted. convention use double, and for normal strings, I use single. Other people might have different, but I think it doesn't really matter too much. Add to objects. So, this is the most important thing. So, now, if I have this one, I do have a doc string that you can access via this doc attribute, or a lot of other tools, like in my notebook, the question mark will do exactly this, access the doc attribute, and shows me the doc string here nicely. So, this This is very important. And the other thing would be the name. So if you look at the name of the function, then you would get the name like this. Yeah? So each function has a string and a name. You can change this name. This is you might call it metaprogramming already. So if you do metaprogramming, you have to know what you're doing. It's very powerful, but it can also be pretty dangerous. So if you change the name of the function, if you want it or not, it depends on you. So if I take the same decorator and the same function and decorate it with my hello, yeah, I put my hello in front. And now if I look at my function and I look at the doc string, then I see my doc string is gone. And if I look at the name, if I look at the name, look at the name, then it's called report because why it's called wrapper, because this is the name of this function I used here, which is not good. Because now the whole thing about decorators is to reuse a decorator. So if I use this decorator 50 times in my code, I have 50 functions called wrapper. That's not nice for debugging. You don't want this one. So that's why best practices is to use a decorator. So from FuncTools, yeah, FuncTools is a function features. So I thought it was a function programming import reps. So we're not lunch yet, there's nothing to eat, but it's a decorator. So you put the decorator here, and you say reps, and you have to tell them what it reps. So it's kind of redundant, but you have to say you rep this function. And this, if you look at reps, it does nothing much. replacing a few attributes, like the doc string, the name, and a few others. It just goes to the original function and copies from there to the wrapper. So the wrapper is still a new object with a different ID, but all the attributes, it looks good enough that you think it's the original function. So if you use an is test with the ID, you will get a different thing because it produces a new object. But all the attributes will be now Much nicer. So if I do the same thing again, so I can do this again. So you have to run the notebook in sequence, otherwise you have to give new names for the functions. Now, if I look at the function docstring, then you see it's still there. Still there. Yeah. And also the name should be much nicer. It's add. So you can look at the code. It's not very difficult. It calls the other function. This one goes through all the attributes, and there's a list of attributes which might change a little bit if you use a different Python function. So if a new Python version gets a new attribute to functions, then they might add it to the list. So if you use this one, you don't have to do it by hand, because for the right Python version, they have the right list of attributes you need to add. Yeah. This can be a bit tricky if you do this inside a class with nested methods or so. It might be strange, because it has a it looks for module, and some functions that are generated dynamically don't have a module attribute, and then you get the error message, no module, then you would need to write, if you look at there, there's this reps called other function where you can specify the list, then you could modify the list if you really wanted to. It's possible. Good. That's best practice. Always use a decorator and do this. Recursion, when you use recursion, then you have to be careful Once you decorate a function, the function will call the decorated function or the original function. And then you might have some, you execute the aspect in every recursion. But recursion is something you should try to avoid in Python if it's possible. It's not very performant. So when you can do iteration over iteration and use recursion only when you have a problem that relents itself to recursive processing like nested nodes or something, that could be something. Otherwise, recursion is not the best thing to do in Python. Good. This is the basics of decorators. Let's look at a few use cases to give you a feeling what you can do. So I have a few use cases. When you unpacked the source code, you should see some of these Python files. So there's a bunch of other stuff in here. Python files, and I just load them here, and I load this cached. Yeah? Load this caching thing, cached. I call it caching. Cached. And this is a very, very simple cache. So don't use it in production. This is just a proof of concept how to make a cache. And I use a decorator. So I use pickle here to generate a key. So the pickle module in Python that might work for you or might not work for you. And you see now we have the same thing. We have this decorator. This is dev dev. So this is a scaffolding. Obviously, if you have two devs, you have to have two returns. Very easy. Later on, we see you can have three devs nested, but we stick with two. So I put the cache in the outer function. This might not be the best architectural decision to do it, because you cannot reach it easily from there. But for this purpose, it should be fine. So a dictionary typically is the first thing you go with is a cache and you could also easily make a persistent dictionary with shell for something like this and then we have our arcs, keyword arcs and you see a pattern very often you call this cached and the nested one underscore cached just because this name will disappear anyway it's just a temporary name so to speak about you have to give the name then you use underscore that's pretty common and you see this dumps is just pickle just creates a tuple with all args and keyword args, so a tuple of the arguments and the tuple of the keyword args. And of course, now the objects have to be pickleable, so if you hand something in that's not pickleable, it's not going to work, yeah? And then I just say, okay, if the key is not the cache yet, then I do call the function Then it produces a result and puts it into the cache dictionary, otherwise you just return it. So caching is usually only useful if this function is expensive. So for our example that we're using now, so if I execute this and I put my decorator cached in front of our add function, yeah, for this function caching wouldn't be useful because the A plus B. If it's numbers, if it's not something very big, you never know. So plus can do anything. But if it's just numbers, this would be much faster than do this caching thing. And now I need to add something here to make this visible. Yeah. And I add adding. Now if I add my thing, now you see it calls the function and shows adding. Now it returns a nine and doesn't call adding. Now it shortcuts it. It just takes a result from the dictionary and doesn't call the function. That's the principle of caching. And with a decorator, you have a kind of elegant way to turn a normal function into a cached function. Yeah? Turn a normal function into a cached function. So that's possible to do. And you could also, very often you apply this decorator with this syntax, but you could also use the other syntax when you, as we used up here, this our syntax thing. So sometimes you want to keep the old function around and make a new function, then just use a different name here, and then you can do it also. That can be useful. That's also a use case. The original function, by the way, is available. So add has an attribute called wrapped, and this points to the original function that is not cached. So it's always there. You can use it. So these Dumble things are typically internal names of Python. You have to know what they mean. Don't invent them yourself. Read up what they mean and use them accordingly. Defining them, you have to read the documentation, otherwise your object might behave maybe not the way you intended. Yeah? So, otherwise, they are useful for reading. When you modify them, you have to really think maybe there's a better way of doing it. Sometimes it can be useful. So, when you do debugging, you can do anything with your program, yeah, and you can do all kind of nasty things, because you throw your code away and you're done with debugging. When you write something that's still a little bit longer than a few hours, then don't do it. There was a question? Yes, and the cache, where is it stored? Yeah, the cache is stored in the function, this is the closure. That's a good question. So if you look at this cache thing, then you look at this, at the ad, they should have this closure, if I'm not mistaken. So see, if we have it in here, we have two closure cells, and you can look at the cells. And the first one is the cell content, and this is my cache. Yeah? This is a dictionary, and this thing, there's a B, there's a bytes, there's a pickle thing. So now I use a closure and store it in the function. This is not... You can still access it, but maybe it's not the best way. Yeah? But just a normal dictionary, and the dictionary was modified, but I could say this, give it the name cache, and I'll say clear, and then remove everything from the dictionary. So each function that has been decorated will have its own cache? Yeah, because you make a new closure. Yeah, so. That's one way of doing it. It's just an example. In the real world, you might want to have this cache as a global variable or use something else to put the cache somewhere else. Make an argument so you can also, then And we see we can use a parameterized decorator, and you can even hand in an existing dictionary as a cache if you want. That would be possible. Yeah? That's just a principle that you can do something with this cache here. Good. So this would be one use case, and caching is a prominent cross-cutting concern because caching usually has nothing to do with your domain logic. You can use caching for all kinds of problems. And that's typically what this aspect thing is all about. You have some tasks that you can need at many different places, and then you have a decorator and you implement it once, and they can use it all over the place. Good. This would be here. The next one is logging. I have another example. So I load my logging, logged, and you see, now I do something differently. I have a constant, a global value, which I call the constant. So constants don't exist in Python, but per convention, if you uppercase the whole thing, you call it a constant, yeah? So now it's the same thing, so we have two level. So we have use our reps, we have our logged here, and now this one inspects the logging. If logging is set, it does logging. So in this case, my logging just is print, it's not really logging, but it's just about the principle. And then the rest we know already. So the only thing different is here this logging. So if it is false, so now I can do the same thing. So instead of, of course, I'm too lazy, I use this, but we don't need the print here. But instead of cached, we say, instead of cached, we say logged, yeah? And we log the thing. have to execute the cell first to get it to go. Yeah, logged. Okay. And now I have called my function. And you see no logging happens because this constant logging is false. Now I change the constant. So changing the constant during runtime is kind of dubious. But for testing you can do it. I change the constant. And now if I do this again, then I do get the logging. So I can turn on and off my logging globally by changing this value, and I don't have to sprinkle a lot of ifs around my code all over the place, because I can only do it in the decorator once. That could be useful. And sometimes you want to switch something on and off. You can also change for positive how much is logged, what log level, and so on. All these things, you know, this is just a principle to give you another example for cross-cutting concern. So you can probably come up with other cross-cutting concerns for things you might need at different places. Good. This is the basics. Any questions? I don't know if the Slido thing, do we have Slido in here? Yeah. So if you have any questions so far. Yeah. If I would want to learn about the structure of the cell contents, for me, it seems kind of, I have no orientation in the cell contents. The cell contents, usually, it's interesting, though, but it's not essential to understand decorators. Because it's stored there under the name when you just access it. How it's done technically, it's not that important, I would say. It's just, if you want to know how it works, then it's a closure, and inside the closure, you have this content. And it's in this tuple, and the order they are used, they will be stored, and then Python will access them, yeah. I think about this, the details are not important to write good decorators for this proof. Mm-hmm? And so is it specific to Jupyter, the cell contents, or would it... No, no, no, no. There's nothing here specific to Jupyter. I just use Jupyter as a tool, because it's easy to present step by step. Everything what I do here works in any kind of your IDEs, just plain Python. There's nothing special about Jupyter, and only the execution is interesting. Yeah? Another question? No. Good. So this is the basics. Now, we know how to write a decorator. Another question? Sorry. So for the logs function, it just has this underscore before, which is like, usually, this is kind of a private method, right? Yeah. This is, yeah, in general, this is a convention in private, but here it's just used because Because this name, it disappears anyway, because this reps makes this function name disappear anyway. Because this one is just a temporary function that doesn't serve any purpose. It doesn't want to have an own name, because it's just a helper. And very often, it's a convention, use the same name as outside, and the distinguish resource use underscore. Theoretically, you can use any name. It doesn't really matter, because this name doesn't appear anywhere, because you want it to go away. Yeah. Yeah. So the underscore is nothing special. It's never special in Python, it's just a convention. If two underscores in the class, it's different, name mangling, but with one, it's just a convention. Pretty strong convention, so, yeah? Good. Okay. Basics, yeah? So you said that this comparison would be false if you have a decorator, I think. Yeah, should, should. The original function, the new function will have, so, of course, if you have add, is is at dot, this is original function. Yeah. Yeah. So is there a way, is there any way that you can compare this and get the truth? No. Both is is object identity. You cannot mingle with the is. You could mingle with the equals sign, I haven't, because equals sign you could do this if this is classes. You could do this instead of function using instances that are comparable, then you could implement the LT, the EQ, the equal, and then you can make it equal. So I'm, because if I use a double equal, and I'm not even sure if it's implemented for functions, I never used it, of course, then you could, yeah, this gives you faults, but if you really want it, then you can use it, if you really want it, you can make this true. But why? I'm not sure. Another question? Yield? No, yield is different. Suppose yield has a generator function, and even if it's called a generator function, it's a totally different piece. It's not a function anymore. Yeah, so if it's a yield, you won't get any, because yield is for this iterator protocol, and it's called generator function, but you could just give it maybe a totally different name, generator whatever, and not function, because you use a function syntax, But as soon as it yields, this thing behaves totally differently than a function, it wouldn't work because it has no return or nothing, it's just not going to work. Yeah? If I write a decorator that does logging, I probably want to apply that to, like, all of my functions? Yeah. So what's the easiest way to make sure I don't do it? Yeah. To all of the functions, there's meta methods. You could go into the namespace because you can use this method. So if you want to do it programmatically, then you would use this syntax up here, which I showed you in this syntax. If you use this syntax, then you can do it programmatically. You only have to get all your functions somehow. The namespace, globals, locals of a module is available. It's a dictionary. You can go through and decorate all of them. That would be a bit meta-programming. Not very difficult. You just have to get all your modules, which you can import, and then you go through the global and then you figure out who's a function and maybe figure out by name which functions you want. There would be a lot of conventions. I'm not sure if it's recommended. It's technically possible. I'm not sure if it's a good concept because it's very indirect. You don't see that they are decorated. So maybe explicit is better than implicit. I don't know how many functions you have. If you have 10, maybe just write 10 decorators. If you have a few hundred functions, then maybe rethink your architecture to have them in a dictionary in the first place or something. Yeah? So it's possible, if you want, because you just can use this one. You can always programmatically apply decorators to functions and replace them. There might be some corner cases I don't cover here, and you might get some surprises with the things you do. Yeah? That's what can happen. It's just a question why you want to do it, and maybe there's a better way to achieve what you want. So I wrote a decorator. You can also decorate methods of a class, and you can write a class decorator that decorates all the methods. That's pretty easy to do. That's not very difficult. If you want to, we can do an example, and that makes maybe more sense because then you have all this. So if you want this, then instead of writing functions, just put all your functions in the class and then use a class decorator. and then say class decorator, give it a good name, and then you can see, ah, all those methods are decorated automatically somehow. Then you say class decorator, take this decorator, then the class decorator decorates all the methods. I don't know if I could throw this out in a few minutes, but it wouldn't be that difficult, yeah? I hope it's not coming up later, but so far we have decorated just the function, the decorator just takes the function as a function. Yeah. That comes up later. That's next. Parameterized decorators, you mean? Yeah. That's the next one. So I will stop now the question, cover more, and we can do more questions later. This is the basics. And since there's basics, there's more advanced usages. And I do make a new notebook, if I can get my mouse under control here. And we say advanced. So now I'm skipping the exercises. And you're encouraged to do the exercises, wrote your own decorator, and this exercise is writing a decorator that uses TimeIt default timer, which is a timestamp, just measure all the functions. Do a time step before, run your function two time steps later, and get the difference. This is pretty coarse. There are better methods, but for an example, it's good enough. Then you can put out a decorator function. You would automatically get the run times of these functions. You can get as fancy as you want. It's there. You can just print it out, or you can store them. And if you call function multiple times, what you do with the time, there's a lot of options there. But this would be the exercise here. So in terms of time constraints, I do go to the advanced section. So if we have our hello decorator, and I want to write a goodbye decorator that says goodbye. So if I have my nice hello decorator somewhere here, that works. This is one without the wrapper. This is a better one down here with the wrapper. So I have hello here. If I have one that says goodbye, I copy the whole code and change the string to goodbye. Right? Yeah? So if you copy a whole thing and change it very little, that should be a red light. This is obviously not a good way to do it. Don't repeat yourself. So if you have this whole thing, just replacing this with goodbye might not be a good solution. So I would make it a more general solution and just add another level. You can solve any problem with another level of indirection. That's what we're doing here. Now I call it say. And I call this text. And I put this in. So I just indent the whole thing. And important, you have to match the returns with definitions. And I export something. Now I can change the names. So here I use underscore say. And here I change hello into text. So hopefully you'll survive all those changes. Text. And here I change rapport to double underscore. So that's... Yeah. Typically, there are not more than two underscores in Python, but that's why we're going to stop. And then I say here return double underscore say, and here return single underscore say. So you see, we just add another level outside, otherwise it's the same. And then how do we get our text from there to there? Again, it's a closure. Now we have a double closure, if you want, because we have another nesting level. And now, I can use my decor, jtrosay, and I can supply the string I want to have here as hello. I could also say greeting, or whatever you want, or just print out, or it doesn't really matter. Def add A and B, so I skipped the doc string here, you know it has to be there. Return A plus B. So, and if I call my function, it does print. Yeah. So now, much better, this functionality is exactly the same, only the data is different, the text. Now I want to hand in the data. So you have to do this. Now, if you want to write this one in one line, this is not that easy. So doing the same thing, so I define this function, yeah? So now I have this say, and this say returns back a new function that calls the function to say add equals, and this one calls add on it. And I hope this is correct. Yeah, I have to get rid of this add sign. So if I do this, now my add should do the same. That's what it's doing. I get a bit tricky, because this first consumes the parameter, or the argument in this case. Hello. Gives me back a new function on this function I call giving the add. Yeah? So if you're a functional programmer, that looks very easy. If you're not so used to a function, it might look a bit scary here. Yeah? But that's what happens in the background, and you can do something like this. So this would be parameterization. And it's used a lot. See, if you use a tool like Qlik, then you always put some kind of parameters in there. Okay, this is a command line command. And then you say, okay, what types or whatever you put in there. If you use Sison, you specify the data types, yeah? And Sison is something you can compile Python to see. And it's useful to specify C data types to make it faster. That's the whole purpose of the thing. Now if I have this, we are not restricted to one decorator. You can always apply as many decorators as you like. Therefore, when you write a decorator, you should never assume you are the only decorator. Yeah, and you see now if I call my function, then it says hello and goodbye. So you can put as many as you like. No restriction. You have to be careful. static method and class method, I think they all have to be the innermost decorator. Some of the built-in ones are a bit tricky, and if you put them in the wrong place, you get a strange error message. So, you have to read up on the documentation, because these are implement, they do something special. Yeah? So, they violate the concept that it shouldn't matter what order you use in decorator, but when you write your own, you better write it in a way that it doesn't matter if it's called the first or the last. Good. So you might have seen some web developments where they have a lot of decorators, a big stack of decorators, yeah? And then if you have more, yeah, then it's typically called the general because it's highly decorated. You know? So that's what they do with this. That's not my joke. I got it from the web guys, yeah? There it is. So, good. This is decoration, and this improves a lot. When you get away with other parameters, do it. If you don't, then you have to use a thing. So now we have this three-level thing, def, def, def. And if you use a tool, like McCabe or so, and analyze the code for complexity, every level of indentation or nesting increases your complexity according to this tool. But we can kind of reduce it a little bit because now I would introduce the concept of a callable. So very often when you say Python and function in Python, then you don't mean function, you mean callable. So I can call sum to sum something up. Or I can call int to turn a string into int. So there are different types. If you look at the type of sum, this is a function. If you look at type of int, what's a type of int? It's a class. A type. If the type is type, it's a class. So it's a class. So here, actually, if I say I call this function int, that's wrong. In my beginner courses, I often say I call the function. It's not totally correct, but it's good enough, because int is a callable, not a function. So you can always check if something is callable by checking callable, callable, and the sum is also callable. Yeah? They are both callable, so you can always use these callable built in to see if something is callable. It means you can write normal parentheses. So I would say parentheses, square brackets, and curly braces. So you can use normal parentheses here to make it do something. In this case, it calls new and init and all kind of stuff in the background. But you don't care. You just use a parenthesis, and you get something back. And if it's a type of a function or type of a class, it doesn't matter. That's a basic principle of Python. Very often, the type of object doesn't really matter. The protocol, the behavior matters, what the function can do. But actually, it's very often not so important. Yeah. There was a question about typing. We don't cover typing here, but typing goes to the direction just way from types more to protocols more about the behavior. Is this iterable? Is this whatever it can do? Is it callable? And so on. Good. Now, we can also make a class callable. This could be interesting. So I make a class. And I call it callCounter. And then I have an init. I have an init. And And myself, and I just set my counter to have a count, and I set it to zero. This would be the first thing I do. And then I do have a download call. So we define a special method call. So now we have to know what it's doing. And I have self here, and I just increment it. So here you could hand in a function or do something more useful. The only thing now it's what I'm doing, I'm increment my count. So to make it a, that's usually a good tip to make the example as simple as possible, but still shows the thing. So for some reason, it doesn't remove the white space here, but yeah, just a thing. So I have my core counter now, make instance, instance, and now if I look at the count is zero. Now I can use parentheses and call the thing. And this does nothing but now the count incremented. And you see the count is incremented. Now this instance is also callable. Not only functions and classes are callable. This instance is also callable because it has this call. So callable essentially checks for the call. So if you look at this, now it's callable. Now we have a function with a state, so to speak. That kind of violates, if you do functional programming, you might get some goose bumps now. Functional programming don't have states, but sometimes you want it. And here, now you can make an object callable, and it can hold a state. So callables could be useful. I think there's also the term functor flying around. I don't know if fancy terms is kind of a similar thing. Now we could rewrite our decorated function as a class, as a parameter-wise one. So we need from functools import reps. So we need our reps again. So I think maybe we have done up there, but it doesn't matter. Now we have a class, say. So here I capitalize. This might be, you might even want to write it lowercase. Typically, classes are always uppercase. But when you use it as a decorator, you might consider writing a lowercase. I've seen lowercase classes, and sometimes it makes sense. But I stayed with an uppercase version here to show that's a class. And now I use my init, my self, and I have my text, which is my parameter. And I have to store my text. And, of course, I use self.text, so an instance variable. And the init has to have two underscores. And then we have our Dunder call. And this dunder call is essentially this thing up here. So I can go and get my internals here. I can take my internals and move them here, so make it a little bit more dunder call. So double underscores are usually called dunder self function. I have to give the function in here. And then I do something like this. So I have to get rid of this one, and I can just use underscores. Whatever name I use, it doesn't really matter. I can just use a say here because it doesn't do anything, so that should be the same. Print and, of course, now I have to say self.txt, yeah? So there might be some other copy-paste problem here, but that's essentially it, and now I should be able to use it in the same way, and I could use decorator. say hello, yeah, at my add function, which I define now the 21st time, at A and B. . Yeah, yeah, thanks, yeah, so I talked about it, but I didn't type it, so thanks. So, the proof, this is the same thing as this triple nested function thing, but maybe easier to understand. So, if you work with classes regularly, you might prefer this, because you can also add other methods and do more in here. Yeah? And this class could do more. But you can write the class that is a decorator and a context manager and an iterator at the same time if you wanted to. I don't know if you want, but it's possible you could. Dyson's doing this. Dyson decorators can also be used as context managers typically. Yeah? Very often. Some of them. And then you can use everything you know about classes and just have this call, and you have this callable, and it might be a bit more organized. You still have the same nesting level, but the class somehow doesn't feel like that deep. Same thing, you have a callable, and if it's getting more complex, you could use other methods, and this call could call other methods, so you don't have so long a function. You could always use a function that calls other functions, but here you have everything together in the class, more organized. So different ways to achieve the same thing. If you write classes regularly, you might prefer this one because you might be used to it and have some other ideas to do here. Good. Yeah? So, just to clarify, on line 24 there, there, the decorator is first creating an instance of the class. Yeah. And then decorating it using, let's say, space, right? Yeah. this question always go back to this approach yeah so this make makes it more makes it clearer so if I say add equals so same thing when I said so just that's what happens so it it makes an instance and then calls the instance with add so first the instance this is in it and then this is this call calls this call and give hence a function there. It is possible to reuse the same instance to decorate multiple functions with the same instance of the class? No, you make a new instance for each class. I'm using the class here. This is the class. As in, I can extract that instance into its own variable then? And then decorate using that instance? You could, but I'm not sure if this is the effect you want. Because... Maybe there's an idea? then it depends that you could if you want to use this if for this one everybody could get the same text you could you can do this so this would be possible so for this one for this special case would be possible uh if if you want this effect so i could uh do this yeah you know something so this should work and now i should be able to uh i don't have to instantiate it all the time and And I can do something like this. So it depends on your use case, but it should be possible. If it makes sense, it depends if you want them to share things, potentially. Because now, for instance, all the functions could share something over the self, if you wanted to. That should work. Yeah? So now... That would work. Yeah? So now I could use it again. But you have to be careful. If this, you're not supposed to, but you cannot prevent it from changing self dot something. It's always possible. It's not good style. I wouldn't expect it, but who knows. Then these functions could communicate over the thing. Probably not a good idea. Yeah, another question? Yeah. Another question? Yeah. So if you go back to... If you're not sure what the decorator does to go back to the explicit line states of the . And I was wondering if that... Like... This one. If you want to use the add equals, say hello, and then put add as a function. Is that what decorator wants you to do? So I'm... Probably I didn't get the question. But this one is exactly... If you unroll this add thing and put it in the function, you see there's multiple function multiple function calls. I'm not sure if I understood the question. I know it notes about, like, if I put add, like, the decorator itself on the function, does Python internally resolve it to taking whatever I put there? Yeah, this syntax with add is, you can only use it in front of a dev, otherwise you get a syntax error, I'm pretty sure. So this dev, this has to be a callable, And then it has to be directly in front of the dev. And this is also now something you, this one, I call this function at import time. So in this case, when I execute a cell, it would translate to, in fact, if I put it in a multiple importer, I do something at import time. So this is some kind of a, you can take over a little bit of the role of a compiler if you want these decorators. So not exactly, but you can do tasks that you can compile. You can check things. Yeah? So compiler check types and so on. You could do similar things here with a decorator if it's possible. Not always, but most of them. So the decorator also takes classes, data class, for example. You mean as an argument here? No, only like you can decorate classes. Yeah, that's the next topic. You can decorate classes, yeah. that you can only put the at sign right in front of the def? Yeah, you can, in the def and the class, that would be a class decorator. For function decorators in front, yeah, you're correct. It's an extra big class decorators, yeah. So, good. I'll show you a few use cases, again, to give you an idea, and then we go to class decorators, which I always would like to cover. So, use cases. The first use case would be argument checking. So it's something you can do. Now we have typing, type hints, and other tools that check for type hints. But maybe you want to do something more. And if just for the fun of it, you could do something like this, just as an example, arc check. And you can check all the arguments. And here I check just for the types, but you could check for anything. So you could check for a number that's between 0 and 100 or whatever you like. That's up to you. Yeah? So I use my func tools and I use a three level approach check check check Yeah, and here I use a star so I make the number of required arguments optional this We'll see in a second what it means the rest is our Scaffolding our boilerplate. Yeah, def def def return return return and here I have I have to make it Maybe a little bit smaller to fit it on the screen. Yeah, so I I check for two things. So these archetypes are the arguments I desire to have, the types, and these are the arguments I get. We will see later on. And if they're not the same number, I do raise an exception, say, you have a type error with these methods that they don't match in terms of length. And then I go through all of them. So with SIP, I can SIP together all the arguments and the types I desire to have and lockstep go to some, and if this argument is not an instance. So in Python, very rarely you want to check this type. Most of the time, you want to check this is instance, because then you also would check for the parents of something. Yeah? That's, there might be cases where you want to use type, but if you don't know, start with this instance. This is more general. It makes your problem more general, and it typically works better on Python. Yeah? So don't take everything as absolute. There are always exceptions. Sometimes you want type. But if you don't know, start with instance, and if it doesn't work, consider type. And if it's not the right type, I'll throw an exception, say, I don't like this. Yeah? So now I could use my decorator, and now I can say arc, check, it's called check, check, check, check, yeah? And I have to define two types. And I say I want to have floats now, and now I could reduce it to floats. So this, you could also put your own classes in there, no problem. And I have two arguments, return A plus B. So maybe I should write a macro to put this in. So I say add three and four. Now, it doesn't work. Yeah, it doesn't work. If I add a dot, then it's happy, because now it floats and it does what it does. You might not want to use it in production. But instead of having float, you could also write a function that you call that is checking something. So you could write your own function that you put in, and then it's checking for some kind of thing, yeah? So a string with a length of five. I'm not sure if you can do this typing. a length of five that doesn't have an underscore on it, it wouldn't work with typing. But you could write any kind of function instead of just an example. Maybe I need to update this a bit, make a new thing, make a check for something else, but you might get an idea what you can do with it. So the thing is, you can use this pretty long check, and then when you apply it, it boils down to this short application. That's the whole thing. And you can use it again and again and again. otherwise you can always do it directly good the next example is a bit different a bit different in terms of what it's doing and it's called registering so I can register something so is there have a registry which is not the Windows registry but it's just an example of something very I want to put something which I can find back later there might be quite a few use cases for the things so you can put something that you can find find back later and I use a a dictionary. This time, I don't put it into the function. I put it in the global namespace or in the module. Now my notebook is the same as a file. And I have this three-level thing because I want to name this different kind of things with a name. We'll see what it means in a second. Function, my function reps. And here I say registry. And this is a dictionary. And I use setDefault. So setDefault is just a trick. So setDefault is going into the dictionary looking for the name, it finds the name, it retrieves the object, and then appends to the object. If it doesn't, it puts in a new list and retrieves the new list, and I can append it. So I don't have to say, okay, if this is empty, put in a list with one element, and the next time I append to the list, I can do this in one line. This is nothing specific. This is just dictionary work. So if you want, I can take this apart. This is like three things at once here. Okay. But this is just appending to the list. And then I do my regular thing so now I have two different ones I have register at call and I do have register at death so if you look there at call first so let's let's do the register at at call so I use my register at call first and I give it a name to have a category and I define my function a and B and I return a plus B returns a bit slow here, A plus B. Yeah? So, and before, actually, we look at the registry, before I execute it, I look at the registry, and the registry is an empty dictionary. So, I can inspect global variables here, so step by step, which is nice in the notebook. And if I look at the registry now, you see the registry is still empty, but now if I call my function, and I look at my registry again, now I know that my function had been called. And if I call my function again, doesn't matter what arguments I use, then if I look at my registry, my function has been just at the same function. So the list gets longer, so it's not particularly useful, but you see something happens, and you can, of course, implement whatever you like. So every time the function is called, I notice this in my registry. Maybe we need some kind of plug-in system or something. So somebody supplies a function somewhere, and then it ends up in the registry or something like this. Good. So now, how to clean my registry? Very easy. Registry is a dictionary. So now say clear, and it clears the dictionary, registry, and everything is out again, yeah? So registry. And now I do the same thing, but I say registry at def. So I'm lazy. At definition, yeah? And now I say complex or something. Complex. And now I look at my registry. And now, something different now, when I define the function, it will end up in my registry. That might maybe be more useful use case for this one. If I call my function, if I call my function, nothing changes in the registry, because when I call it nothing, it's different, yeah? So whenever somebody is using your decorator and defines a function, it will end up in the dictionary. can collect functions somebody else defines for what purpose ever. And you see now it's a bit different. It's two level. And see, here I return the function unchanged. No star, double star game here. Nothing. So I don't replace original functions. I don't need reps here because I don't do anything. I return the function unchanged. But before I return it, I know that somebody defined it. That's why the registry. So this is a use case where I mentioned This happens at import time. So if I import my module as a decorator, this will be executed. And I know somebody, some module I imported, somebody defined a function that's supposed to be somewhere, when there's supposed to be something to do something with it. Yeah? Is there a reason you used dict.setDefault instead of defaultDict? Yeah, I thought about it. But you have to import from collections to defaultDict. You can do the same thing. This is easy enough here. That's why I don't have to introduce another data structure, but would we do the same thing? The default tick can do a bit more even than this one, yeah? But you're right. Collection module has a default tick that's just doing this one. You just have to specify that it's supposed to be a list in the first thing, and then we do it, yeah? But this would mean import, and then you have to initialize it two more lines, and I'd say, okay, that was argument. Good. Okay, this would be the example for registration, which is a bit different, and you can solve a kind of different problem what you wouldn't have. So these are function decorators starting from 2.6, I think, if I'm not mistaken. We have class decorators. Good messages, class decorators are a bit simpler than function decorators. And they are useful. And class decorators can take over quite a few things where in the past you used metaclasses. Anybody used a metaclass? Yeah, a few people did. So a lot of things you did with metaclasses you can do with class decorators. Not everything. There are a few things you still need metaclasses, but very few. And I never found a use case for these things in any production thing. So it's not that common, obviously. And also, there's an initSubclass method, which is new, which makes it easier, because we will see class decorators you can inherit. You have to apply them on all the childs, the children don't get it. So let's look at class decorators, which we mentioned already, class decorators. So what's a class decorator? So I just write a function, whatever it does. It takes a class, and the most important thing, it has to return this class. So now you're totally free to do whatever you want. So this is the boilerplate you want. Now you do whatever you like with the class. And you could, I modify it, but you can also just inspect it, which might be the more use case. Yeah? Now I say, okay, class, class.new, new attribute or something, yeah? New attribute something. Now I use this decorator, and now you are correct. You can use decorator syntax here, and I can say mark, and I can mark my class, class A. a. Yeah. And I don't put anything in there use pass. And if you look at the class, now you see, it does have the attribute new attribute. So this does exactly the same. So this is not very difficult. So this one is doing this. Yeah, so it gets a class a Oh, and puts in in the class, and this is exactly the same as the decorator's doing. But again, you don't have to, a class definition can be pretty long, you have to look at the very bottom of your class after 50 lines or 100 lines or 150 lines to see that this class is modified. You can make it very visible on top that this class is modified, and that's essentially what happens, yeah? Much easier, no nested function definitions, just get the class and return the class, and do something with the class. In this case, I add something that, yeah, maybe not a good idea, but you can also inspect the class and do something with the class. And for this, I have a use case, and I've forgotten the name of the file, but I can always check, method name check, load method name check. So this is something, this is a bit more involved class decorator. Actually this comes from my tutorial about metaclasses, I used to do metaclasses first, but you can use it as a class decorator. So the thing is, I would like to inspect all the methods of the class, and if there are more than 30 characters, I don't like them. Yeah, you see. You can change the max length here, I put some doc strings in here, and these are my things. I get my class, and now it's a bit more evolved. I define there's a decorator here, and since I need to have two levels per course, I give it a parameter. Same thing as before. This is a parameterized one. So I give it a parameter. We have the 30. I get my class here, and I go to the dict of the class. So the dict of the class contains all the attributes. As a dictionary, with items, I get the key and the value, which I call name and object, which is more useful names here. And if it's callable, so if it's a method, then I check if the length of the name is more than my specified 30 characters or whatever I specify. I don't like it. And I generate error message, which I put together here as a string, say, okay, this is here. That's what I wanted. But you cannot have names longer than this and this. Okay. So let's apply it first. We can look at the source code in a second. So as I go in here and say check nameLengths, and I define my class B, yeah, with a method which is fine, method self, method self, so when you talk and type, does nothing, pass, More dev method method with a very long name that is not nice. So I don't know some of the other people do this. I don't do this one. Okay. Okay. Now, do this. I made a syntax, I forgot the column. But when you put the column and you do this, yeah, why doesn't it work? It should give me an error message. Yeah, the parameter. So I have to put the parentheses there to make it work. So there's also a way to make it this parentheses. And now I cannot do this. It doesn't like it and give me the error message and say, okay, this name has 43 characters, only 30 are allowed. Yeah? This could be useful. So you could apply, you could check. And this one doesn't even import. So there's no way to start your program. Of course, if somebody just comments out a decorator, it works. But that's over there. That's going away. And now you could also think, OK, since I go over the callables, now I could say, please decorate all methods with this decorator. So as an argument, I give in a decorator, and then have this one going over all these callable objects with all the methods and put the decorator on this syntax, replace it in the dictionary. Maybe not use the set attribute might be more... Yeah, set attribute is pretty much the same thing as replacing a dictionary, but there are a few things that don't go to dictionary, like properties, and so you have to be careful. This one you have to figure out what works for you, but this would be possible. So we have a class with 20 methods, you want to decorate all of them, You write your own class decorator and say, okay, please decorate all these methods with this decorator. Maybe that's a different approach doing this, but it would work. It would work. Good. I forgot another example here. Yeah, this is temperature check, but that's fine. Good. declarators are conceptually much more simple than a function decorators but nevertheless can be useful good we still have 10 minutes and they're pretty much true yeah questions In the end, it's just functions that return something. I don't think we need any special testing method for this. So you have, it's a function that returns a new function. So I don't see anything particularly different than testing other functions. So maybe we need to make a use case for this where we say, okay, this is a decorator and how do I test this decorator? I mean, you have, at some point, nesting of three levels, right? Yeah. Yeah. Yeah. How, for example, would you approach testing, like, the second level, dedicatedly ignoring the other two levels? That's not that easy, I think. Because you have to technically put it inside. You cannot just write it outside and then inject it in there. Otherwise you could, like, have this inner function tested by itself and then just give it a parameter to put it in. I'm not sure if this is possible. Because it's the scoping thing. That would probably mean use something, I don't know why I do this. I'm not sure if it's possible. I have to check. But the whole thing is one thing. So probably what I would like to do, I just write the scaffolding, and then this one calls a function that does the work, and you test this function that does the work. Because you only need a scaffolding. You only need this def, def, def. And then inside whatever you do, you call another function that does the real work, and you give it the parameters and get the result back. And you do the more complicated stuff in a separate function that does the work and not directly in the decorator. And then you could test this function separately. That might be the most sensible approach. Just divide and conquer. Question? This three-level structure, can you just do two decorators in front of the innermost thing and use that as a decorator again? I don't know. What do you mean, use two decorators in the innermost thing? So that's a three-level. This check is a three-level one. Yeah. Basically, you could have check as a function, check underscore as a function, and place these two functions as a decorator over check underscore, underscore, check. Would that work? You can decorate a decorator, probably, yeah. So for this one, I cannot say yes. I have to test it out, because it's like chess. You can only think so many moves. But it probably would work. You can test every function itself. Is it that stuff? Decorating the decorator? This one is decorating in here, inside, in the func. That's totally fine. Yeah, probably you could do this. But again, if you take this guts out here and put it in our own function, that's you want to do in this one you want to test write in their own function in a helper function that you can test separately and they just call the function here and hand in this ox there's only dogs in the and you just have to make sure this to to reference these guys to put them in the closure otherwise it wouldn't be there yeah so but it's maybe the easiest and there is an online question what is your opinion of using decorators for error handling I haven't used it for error handling those exceptions I'm not sure but how you can use error handling so there might be use case I'm not aware of a use case and I don't remember seeing one I haven't used it for our handling so I have no experiences and potentially possible but I don't know what how exactly I don't think so but I don't know haven't tested it but it seems like the same amount of work but I wouldn't I wouldn't need to test it so I don't know so but seems like the number of calls is the same so it shouldn't be a big difference yeah Yeah, class decorators are for classes, not for instances. So if I, so it's not possible, so I would have to decorate the inner function and use that to... Yeah, you can, as you can use it, we have five minutes left, so you can always, so this This one would always happen at definition time. But if you use this one, this one you can do any time later on your code. And you don't have to do this. You can always make a new class with a new name. So you could always, instead of this, you call this, so you keep the old one and just give it a new name. Yeah? With the class, you would do something like this. That's totally possible. And this one you can do any time in your code. would be possible do you do you need a decorator okay so if you don't want to implement it in the class but Yeah, actually, I have something, I do have a meta class tutorial where I just replace, there's a function called generate, do you want the classes or the instances? The instances. Then you can implement it in the class. I think we don't even need decorators for it. You just have a base class inherit, and then you use the init, and the init just counting how many times. It's very difficult about this thing, because you might create new references, and this one gets deleted somewhere, you keep the reference right. You might want to use a weak reference stick for this which is not that easy So these kind of meta things can you might generate some phantom effects. You don't want this It's function calls so function calls are pretty expensive in Python compared to other languages for sure and you always add a bunch of function calls. So it could impact performance. It depends how often the decorator is called. So my register one is called only once. Usually it doesn't matter because it happens in port time, which is expensive anyway, so you wouldn't see a difference. But if you call the function that's decorated that goes through all this jazz to find it millions of times in your code, you probably see a difference, especially if the rest of the function doesn't do much. If the function does a lot, then this extra overhead is presented by a small, but if the users doing one plus one, like here, then the decorator takes way more time than our A plus B, in our case. Yeah. It could have an effect, but again, this depends very much on the use case. Another question? Do you have maybe more use cases for decorators, or, like, favorite use cases, possibly in a data science, data memory context? So, in general, everything that has nothing to do with your specific domain language you you want to reuse in one way or the other. And objectively, you could use inheritance, but then you always have a parent class, and everything is inherited from this class. This would be one way of doing it. And with decorators, you can get anything that's cross-cutting concern. And probably you can come up with something and say, OK, I programmed this, and here I programmed pretty much the same thing, and here I programmed too much. I think three projects are totally different, but I do a very similar thing. The logging, the timing, profiling, these are kind of these cross-cutting concerns that can be useful. And Sison and Mumba use it to specify data types because they compile to other languages. So if you want to go out, so if you access a database, you might, I don't know, give the column data types with a decorator or something. I'm not sure if this could be a use case. So not everything that's possible is the best solution or might be easier solution, yeah? So I don't want to sell you decorators. It's one tool in a tool chest you can use. There might be easier solutions, but for the fun, you can try and decorate it. Good. I think we're out of time. Another question? I believe both Qlik and even FastAPI, I noticed some of the libraries make the empty parentheses optional. Yeah. You have to check. Here it's not optional. You have to recheck how I am called. This is our metaprogramming. So you have to check if I'm called with a parameter or not and go from the three-level to the two-level version. It's possible, but I haven't put it in here. It's our metaprogramming. I couldn't do it right now. I have to look it up, but there are some Stack Overflow questions about it, I'm pretty sure. It's a simplicity of segmenting. Yeah, yeah. So you have to check if I'm called with a parameter or without or if I'm used without the parentheses, I just kind of add empty parentheses to it, virtually, or something like this. Good. Thank you very much. So if you would like to have more teaching, I have some flyers here. If you want more Python things, I do this for a living. So if you want me, I can come and teach you more. Otherwise, thank you very much, and I wish you a good lunch.

Mike Müller

About — in the speaker's own words

I've been a Python user since 1999, teaching Python professionally since 2004. I am also active in the community organizing Python conferences such as PyCon DE, EuroSciPy, and BarCamps. I am a PSF Fellow and chair of the German Python Software Verband.

Social card for talk: Aspect-oriented Programming - Diving deep into Decorators