 All right, we're back again with Anna Lena. Anna Lena, I'm sorry. Who is going to talk about finding magic in Python? She enjoys using Python for her personal projects and professionally. She has worked as an AI resident at Microsoft Research and is currently working as a machine learning engineer at Innowex in Germany. Where in Germany are you streaming from? I'm from Bonn. That's close to Cologne. I guess that's what most people know. Okay, over to you, Anna Lena. Yes, thanks a lot. Yeah, as you mentioned already, my name is Anna Lena and I'm really excited to be here today. And I want to use the next 45 minutes to take you on a magical journey through the world of Python. And I hope that you will enjoy it as much as I did when preparing this presentation. Just a few words about myself. I'm working as a machine learning engineer in a German company called Innowex, where I work on various projects surrounding data engineering and machine learning. In my free time, I'm working in a voluntary project called KI Marschule. KI is German for AI, where we teach kids in German schools about AI and machine learning. And I like working on personal projects and usually share those with the community either on GitHub or my personal webpage. And the two most popular repositories you might find interesting are Machine Learning Basics, which implements fundamental machine learning algorithms in plain Python. And then there's the Magical Universe, which is the basis of today's talk. So I started the Magical Universe a few years ago when I decided to participate in the 100 Days of Code Challenge. For those of you who don't know the challenge, it's about spending some time, even if it's only five minutes a day and writing some part of code. And I wanted to look into some of the Python concepts I'd been using for a while, but also some new ones. And because I absolutely love Harry Potter, I decided to work through the concepts using my own Magical Universe. And this really turned the coding challenge into a lot of fun. So let's jump right into it. In my opinion, every good talk needs a story. So today's presentation is based on a Magical Universe I created called The Tales of Castle Kilmere. And I want to present the main story and characters to you such that we can work with them throughout this presentation. And then the goal of the presentation would be to create the world of Castle Kilmere using Python. The main character of our universe is called Lizzie Spinster and she's a quiet clumsy girl of age 12 who loves to read and is therefore sticking her nose into books most of the time. Her best friend called Luke Berry is living next door and has a completely different personality. So he's usually full of energy, full of mischief and is constantly trying to convince Lizzie to play tricks on other people. One day, Lizzie's parents decide to send her to Castle Kilmere, a well-known magical boarding school with the hope that she will become more open-minded when being surrounded by many other children, but only after Luke's parents allow him to go to, Lizzie agrees. So Castle Kilmere is a really old school which has been around for centuries. No one really knows how old it is, but it is very well-known all around the world. The headmistress of Castle Kilmere is called Miranda Mirren. Although she's a very strict and stiff witch, she cares greatly about extracurricular activities and her favorite is the Club of Silly Walkers which she has presided over since she joined the school. Castle Kilmere is divided into different faculties. Each faculty is focused on a certain subject and is supervised by a professor and naturally all students have to take certain mandatory courses like spellcasting, but they are also free to choose courses from other faculties. And on the first day of school, Lizzie and Luke quickly learned that actually not all kids at school are nice, but some kids and their families have been associated with an evil magical society called the Dark Army where the Dark Army is led by a particularly dark wizard called Master Odon. Not many people who know how he looks like, but everyone knows his name because he has been pulling the strings behind the wheelings and dealings of the Dark Army for years. Yeah, so this gives us the main information we need for the presentation. There's lots more to discover in the project, but this will get us started. So we will start out with some simple Python features and then progressing to more advanced concepts in the end. To get started with the universe, we have to talk about classes first. Using classes in coding is tightly coupled to the concept of object-oriented programming. In this concept, programs are designed by creating objects that interact with each other. And where each object can contain attributes and methods. Object-oriented programming can be used to represent the structure of the real world. If you think about it, we have lots of classes actually in the real world and instances of these classes. For example, there would be the class person and then individual people like you and me who are instances of this person class. And so what is a class? Precisely, a class acts as a blueprint for an object. It describes how objects, so-called members of the class are structured and which attributes and methods they have. The syntax for creating an empty class is quite simple. So we start our universe with the class Castle Kilmer member. At the moment, the class does not contain any code. So we use the keyword pass as a placeholder in the body of the class. And yeah, if you don't know pass, pass can just be used to denote places where we will eventually put code. Right now, it allows us just to run this class without getting an error. Now, if we want to create an actual object and so-called instance of the class, we have to instantiate the class. And this is also very simple as we can see here in the second line of code. And we just call the class and assign the result to a variable called Kilmer member. Right now, this class is quite boring. It's not very useful. And we want to add some attributes and methods to make it more interesting. So each Castle Kilmer member will have a name, a birth year and a sex. So we know whether the member is male or female. And this information is included in the so-called DunderInit method. If you don't know what the word Dunder, it stands for double underscore and is typically used when talking about methods that start and end with two underscores. So this DunderInit method is called under the hood whenever you create a new instance of the class. So when creating a new member, for example, our headmistress Miranda Miren, this DunderInit method is called automatically with Miranda's name, her birth year and her sex. And the init method then returns an instance of the class which is assigned to a variable called Miranda. Notice that the first argument of the DunderInit method is called self. This points towards an instance of the class whenever the method is called. And also to the class, we have added another method called says that adds behavior. So in this case, it allows our Castle Kilmer member to say something. So when we call Miranda says hello my dear, Miranda would greet us. So these are notes about what I just said. And the DunderInit method is called under the hood when creating a new instance of the class. And as mentioned, its first argument is self, which points towards an instance of the class whenever the method is called. So the Castle Kilmer member class is nice, but of course we want many other classes in our universe like pupils, professors, ghosts, and so on. But these are all members of Castle Kilmer, right? So we can express this relationship using the concept of inheritance. Oh, sorry, I forgot to click on words. So inheritance allows us to create a new class that inherits all attributes and methods from the parent class. And the resulting child class can override methods and attributes of the parent class and it can add new functionality. Inheritance is great if you want to reduce duplicated code, but also if you want to show the semantic relations between objects. So let's use the concept of inheritance to create a pupil class. So this new class inherits from our Castle Kilmer member class that's up here. And in the DunderInit method, we use this method super to call the DunderInit method of the parent class of the Castle Kilmer member class. And this initializes name, birth year, and sex. Then we add new attributes, which are specific to our pupils. For example, they started school in a specific year and they might own a pet. And then there's another attribute here called elms, which stands for elementary level of magic and contains all the obligatory classes a pupil has to take. So when creating a new pupil, she won't have passed any elms yet, but of course this changes once he or she starts school and starts writing exams. Okay, so we can now create pupils. For example, our main character is a spinster. For this, we call the class with the necessary argument like name, birth year, start year, and so on. So let's sum up what we learned in this first section. Classes in Python implement the paradigm of object-oriented programming. They act as a blueprint for an object and describe what properties and behavior an object should have. We created the class Castle Kilmer member and used inheritance to create a child class pupil. And the child class inherits all methods and attributes from the parent, but can add new behavior and also override existing behavior. Then we talked about that inheritance is good for showing semantic relations and for avoiding duplicated code, but I have to say that you should be careful not to use too many layers or levels of inheritance because it adds complexity and can make your code harder to read. And we now have Castle Kilmer members and pupils, but we could equally use inheritance to create classes like a professor and a ghost class, but for now we will stick with the pupil class. Okay, so this concludes our first section. Now that we have some classes in our universe, I want to take a look at the different types of methods that we can add to a class. So a class can have three types of methods. There are instance methods, class methods and static methods. And we will start with the most common one, which are instance methods. Instance methods are the most common type of method. They take at least one parameter called self as an input, which points towards an instance of the class when the method is called. We just learned about that in the first section. An instance method can modify object state using the self parameter and class date indirectly when using the class or self.dunder class parameter. And we have seen an instance method already in our base class. So we have the says method, which takes the input self and a string of words, because the Kilmier member should say, and whenever the method is called on an object, the self parameter can be used to access attributes which were set in the dunder init method, like self.name. The next type of method we will look at are class methods. So class methods look similar to instance method in the sense that they take at least one parameter as an input. This parameter is however not self, but CLS, CLS for class, which points towards the class, not a particular object instance when the method is called. And therefore a class method can only modify class date, but not object state. But of course now if you change class date, this affects all objects. Okay, so another important thing to know about class methods is that we have to use the add class methods decorator when implementing a class method. And we will talk about decorators later on right now, just remember that you put an add class method on top of the function, and we will see an example in a second. And one important thing to note first is that the names of the variables self and CLS are only conventions, it's best to follow them, but in theory you could also give these variables different names. Okay, so let's take a look at what class methods are good for. One nice way to use class methods is as alternative constructors. If you haven't heard of this before, you may now think what is that supposed to mean? So in Python, we can have only one constructor per class, the dunder init method, but we can use class methods to write functions that allow us to create objects that are configured exactly the way we want them. And in this way they act as additional constructors, these class method. For example, we probably want to create the main characters of our magical universe. And when we would have to type the names and attributes every time, this would be very slow and annoying. So we can speed up the process by adding a few class methods. In this example, we have a class method for Lizzie our main character. And with this, we can create the class method very easily. By just calling pupil is the name of the class, Lizzie. And this returns a perfectly set up instance of our main character. And you can equally create class methods for other characters like Luke Berry or our headmasters Miranda Mirren. Okay, last type of methods, we will look at our static methods. They take neither self nor CLS as an input. Therefore they cannot modify object state nor class state. But yeah, so they are related to the class but they are still independent. And that's because they can only access data they are provided with. Static methods are a bit hard to understand, I think. So let's look at an example. If you remember the pupil class, we had this attribute called elms for elementary levels of magic. And this contained all the obligatory classes a student has to take. And if the class is passed or not, depends on the grade that is written. So Castle Kilmere has a fixed grading scheme ranging from excellent to horrible. And this is actually perfect material for a static method. So the static method we created here gets a grade as an input and then returns whether or not this grade means pass or fail. So you can see here that the method uses no class or instance state but has access only to the attributes it's provided with which is the grade it's given. And we can now call this method on different pupils. For example, we can see if Lizzie passed when she writes an O which stands for ordinary or with an H for horrible. And since the static method is not bound to a particular object instance, we can also call it directly on the class pupil. And one thing, another thing to note here is that we up here, we use our newly created class methods to create the instances. So this already saved writing some code with the names and the other attributes of the two characters. Okay, so you might now wonder why we don't use normal methods for these applications. So why do we really need class and static methods? A clear advantage of not only using instance methods but also class and static ones is that they allow a developer to communicate what intention she had in mind when implementing the method. For example, a static method expresses that the method is independent from the rest of the class. And another application for class methods would be that they can be used as alternative constructors. Okay, so this concludes our sections on the different types of methods that can be used in the class. We have learned that classes can have three types of methods. The most common one are instance methods and then there are also class and static methods. And one thing to note before we go on is that static methods or using them is quite controversial. So some people advise against them but personally I think that in some use cases they can be useful. Okay, so going on to the next section. The next topic I would like to cover is the default DIC class. The default DIC class is part of the collections module. This is actually a really cool module and I suggest that you check out the other features it offers. For our magical universe we will look at the default DIC class and how it's used. Okay, so let's say we want to extend the castle kill me member class with an underscore trades attribute. So we add a new attribute to the Dunder init method which contains the character trades of the castle kill me member and we further add function which allows us to add attributes to your character and then one which just nicely prints the trades. So with this functionality, we can add trades to Bromley or we haven't brought Bromley yet and Bromley is the school's caretaker. So he's a very kind man who is tidy-minded since he's a caretaker after all and but there's something he's not which is impatient. So we can account for this personality now by adding different trades to the Bromley instance. So this can be seen here. And what we now want to do is that we want to add a function which checks if the castle kill me member exhibits a certain character trade or not. And as the type annotations here show this function should receive should accept a string trade and return whether or not the character in question or the person in question exhibits this trade. So what we want for this function is to return false if it's certain trade cannot be found and one way to do this would be to use the dig.get function but another even more powerful way is to use the default class of the collections module. Let's first look at the dig.get function. This allows us to provide a default value which is returned if a requested key cannot be found. So if a member possesses a trade its value is retrieved from the underscore trades dictionary and returned if the key cannot be found we just return false. So this would be a simple solution but there's another one namely the default dig class. So the default dig class is a subclass of the general dictionary type in Python. It behaves like a normal dictionary in most cases but has one important difference namely that it accepts a callable in its constructor whose return value will be used if a requested key cannot be found. So when you access a missing key this key is created and initialized using the default factory. The basic usage is quite easy we import the default dig class from the collections module and instantiate it by providing a default factory. The default factory must be some callable which returns a value and the returned value will then be used if a requested key cannot be found. So our goal for the exhibit trade function was to return false as a default value. This means that if a requested key cannot be found which is a trade in our case the default dig should create an entry for that key with a value false. So how about providing false as the default factory when initializing the dictionary? So take a second to think about why this is not working. Okay, so we cannot provide false as a default factory because the default dig class requires a callable as an argument and the boolean false is not a callable but a boolean variable. So instead we have to define a function that returns false when called without arguments. Such a function can be seen here it simply returns false when being called. And with this we can create our default dig to providing it with the return false function as default factory. Alternatively you could also use a lambda expression here but since we haven't talked about lambda expressions I will stick to the normal function for now. So what is so great about default digs? The current behavior. So right now we saw that there's the dig.get function and the behavior of our default dig right now is quite boring. So why do we need default digs? The power of the default dig class arises from the fact that it can be provided with any kind of callable and this has several important use cases. One common problem that can be solved where the default dig class is grouping items in a collection. So let's say we have a list of some of the pets Casa Kimia pupils are allowed to bring to school and we want to group the pets by type. So that means having all the owls together the cats together and so on. And you can achieve this by providing the callable list as an argument to the default dig. So we create a default dig using list as a default factory. And then we loop through our list of pets and our first item in the list is cotton. That's the look of the owl of Luke, Lizzie's best friend. And when we try to find this key owl in the dictionary it won't be found. Since we try to find, so since we used list now as a default factory a new empty list will be created and inserted into the dictionary for the key owl. We then append the name which is cotton to the list and next time we look for the key owl it will already be contained in the dictionary. We can just append the new name to the list. So what do you think the output will be when looping through the resulting dictionary which we used for grouping the pets? Well, that's exactly what we wanted. We get a list of all the owls, a list of all the cats and so on. So there are more use cases out there. For example, you could use a default dig to count the number of pets of each type. And I don't have this in the slides right now but I'm confident that you will be able to solve it yourself. Okay, let's sum up what we learned. We started out with a common problem that often occurs when working with dictionaries namely accessing or modifying keys that don't exist. We learned that this can be solved using the collections.defaultdict class. A defaultdict behaves nearly identical to a regular Python dictionary with a subtle but important difference namely that it accepts a callable and squad structure whose return value will be used if a requested key cannot be found. And this can be useful in several situations. For example, when you want to group objects like we grouped our pets. Okay, so let's continue our journey with a concept probably most of you have heard of, decorators. Decorators are quite advanced and can be a bit tricky to understand in the beginning. So don't worry if you don't fully understand how they work immediately. The more you use them and read about them the easier or clearer the concept becomes. So what is a decorator? In simple terms, a decorator is a callable that takes a callable as an input and returns a callable. What is a callable? So typically when we're talking about callables we mean functions. So for simplicity, I will stick to functions from now on but you could equally decorate any other callable like a callable class for example. So decorators allow us to extend or modify the behavior of a callable and of the callable that they take as an input. And they do that without permanently modifying this input function or class itself. The behavior of the class is only changed, sorry, of the method and this input function is changed only when it's decorated. So this quines sounds quite abstract. So let's look at some code. The simplest decorator would be one that just returns its input function. Right now this useless decorator is not doing anything. It receives a function and returns that function without modifying the behavior in any way. Now we can apply a decorator to a function by wrapping it. We, so given our simple useless decorator the one that is just returning the input function we can apply it to our say hello function up here by providing say hello as an input and assigning the result to the variable say hello again. This way of applying decorators is quite cumbersome. As you can see here, we had to type say hello twice here and here. So the more common way of applying decorators is to use this add decorator syntax which is simply syntactic sugar that prevents having to write say hello multiple times in our case. So we could achieve the same behavior as before by just writing add useless decorator on top of our say hello function. This is the same as when you create a class method or a static method with the add class or add static method decorator. So right now the output of the say hello function is not changed at all since our useless decorators not doing anything. So let's see how we can extend and modify the behavior of the wrapped function. So when we want to modify the behavior our decorator must be a little more complex. Specifically, it has to define a new function called the wrapper function. And this new wrapper function is then used to wrap the input function and modify its behavior. An example might look like this. So let's go through this step by step. We have our decorator function which is called goodbye. It accepts a function as an input and then defines this wrapper function internally. The wrapper function calls the given input function and stores the result in a variable called original output. It then appends the string goodbye have a good day to the original output and returns this new output. And then the decorator just returns the wrapper function. So when we apply this new decorator to our say hello function, our output should have changed. So what do you think it is? So the output of this print statement. I hope that's what you expected. So when our say hello function is decorated with a goodbye decorator, the output changes to hey there which is the original input. And after that goodbye have a good day which is the output or the string added by the wrapper function. So this was a very simple function now without any input arguments, but how do we decorate a function that has input arguments since we also want to be able to decorate these kinds of functions. For example, we might have a function that allows a person to talk to another person such that Lizzie could say hello to Luke. So how could we decorate this function? What we have to make sure is that our goodbye wrapper function can somewhat, there we are, can somewhat process the arguments, person and words. And this is actually not too hard. We simply use the arcs and quarks variables to collect all positional and keyword arguments. And then forward them to the original input function. So our code looks similar to the previous version. The only difference is that the wrapper function now collects all positional arguments in the arcs variable and our keyword arguments in the quarks variable. And these are then forwarded to the input function. The rest of the code state the same. And yeah, take a guess now at how the output of the print statement say words Lizzie, hey Luke, will look like. Okay, so this works very well. So we can now decorate functions with arbitrary input arguments. And we won't dig deeper into the topic of decorators now. There's lots more to discover and it would be a whole different talk to just go into the magic that can be done with decorators. So as a last piece of information, I want to ask you why you think, or what do you think why decorators are called decorators? I think that's a nice memory hook if you can't remember what they're doing. So think about that for a moment. So actually we call them decorators because they decorate other functions and allow us to run code before and after the wrapped function is executed without permanently modifying its behavior. And this closes our section on decorator, decorators. So as a quick summary, we learned that decorators allow us to modify the behavior of a function. We also learned that the decorated function only changes its behavior when it's decorated. And this could be or can be very useful. For example, we could implement a decorator that determines how much time it takes for our code to run. And when using this decorator during development, we can time various parts of our code base but remove the decorator again once we have we are done with all the optimization. Yeah, the last point is that decorators are a really complex topic and that this only scratched the surface of this topic. Okay, going on, we have abstract based classes. So we created several classes and methods in the past 30 minutes. We have a parent class called CastleCumieMember, several child classes. And as we learned in the beginning, the child classes inherit all methods from the parent class, but there are other more advanced applications where simple inheritance is not sufficient. And this is where abstract based classes come into play. Abstract based classes are useful if your application involves a hierarchy of classes. In particular, in this hierarchy, it should be impossible to instantiate the base class. All subclasses should have a common base class and all subclasses should implement certain methods defined in the base class. So before jumping to further explanations, let's look at an example. In our magical universe, we have lots of different types of spells. For example, a spell could be a charm or a curse. And this makes the spell class a great application for Python's ABC module. To use an abstract based class, we import Python's ABC module and let our class inherit from the ABC class. And then we flag the methods that must be implemented by all subclasses with the decorator at abstract method. So a spell should have a name, an incantation and a certain effect. And also it will have a defining feature and a cast method. Notice that these implementations are abstract ones and not concrete. So the ABC specifies the requirements of being a spell, but it does not tell you how to be a spell. We can introspect an ABC to find out what abstract methods it has. And this is especially useful if the ABC you're working with is not your own. So let's test if we can instantiate the base class spell using a simple spell called stuporis raziato. For this, we call the spell class with a name, an incantation and an effect. So this actually doesn't work. It gives us a type error, which is exactly what we want. Since for an ABC, it should not be possible to instantiate the base class. So let's continue with creating a subclass. So we create the class charm, which might look like this. It inherits from the base class spell and naturally they're easy and difficult charms. So we will add two new attributes, difficulty and minimum here, where this specifies at what year students are able or allowed to cast the charm in question. And we add an implementation of the abstract method cast. So let's try to instantiate this charm class, again, using stuporis raziato as an example. Okay, so we saw already that we cannot instantiate our base class spell, but when we try to instantiate our subclass charm, it doesn't work either. So why do you think that's the case? So this instantiation statement raises an error at instantiation time because we forgot to implement the defining feature method. And this highlights a big advantage of using abstract base classes. Let's say we have a subclass that doesn't implement all methods required by the base class. When not using abstract base classes, we notice this only by getting an error quite late, when namely when we call the method that is missing. With abstract base classes, we get an error already at instantiation time. So for proper implementation of the charm class, we would have to add the defining feature method. So this implementation now contains everything that is required, an implementation of defining feature and the cast method. So we should now be able to instantiate the charm class, which works well, as you can see here. Okay, so note, or an important thing to notice that ABCs only check for the presence of methods, not if they are properly implemented. So in our charm class, we could implement both required methods by just putting pass in the body and no logic at all. And when you notice yourself writing abstract methods or not implementing them properly in several of your child classes, this is probably a sign that this method should not be an abstract method. Let's summarize what we learned. ABCs allow us to formalize the relationship between a parent and a subclass and they serve three purposes. First, they let the parent class communicate that subclasses should have a certain structure. Second, they allow classes to identify themselves as having the required structure so that is meeting the demanded requirements. And third, they enforce that a subclass meets the requirements, otherwise throwing an error at an exception at instantiation time. So lastly, I want to mention that also similar to decorators, ABCs are a huge topic and that you have seen now only a small part of it. But nevertheless, I hope that you have an idea now how they work and why they can be useful. Okay, so our last section is coming up, which is about named tuples. So in this section, we will look at named tuples and before we do that, we should review what a tuple is. So in Python, a tuple is a simple data structure that can be used for grouping arbitrary objects. It's important to know that tuples are immutable. That means that once a tuple has been created, it cannot be changed anymore. So we already use tuples in our magical universe. For example, we defined the pet attribute of the pupil class to be a tuple and each tuple has two fields, the name of the pet and the type. So Ramses, Lizzie's cat would be an example of such a pet. Each field of this tuple can be accessed using its integer index. For example, we can get the name of Lizzie's pet by looking at the value at index zero. As I already mentioned, tuples are immutable. So once we create a tuple, we cannot change it anymore. And you can see this in the example on the slide. When we try to change the name of Lizzie's pet to Twiggles, we get a type error that tells us that we cannot assign new values to the items of a tuple. So what are named tuples? As the name suggests, they are a variation of rather an extension of plain tuples. In particular, they allow us to name the fields of the tuple, which makes it easier to access the individual fields and it makes our code more readable. So in the plain tuple example, we were only able to access the values stored in the tuple by using integer indices like Lizzie's pet at position zero, which is fine when not having too many fields but becomes messy when having three or more fields in the tuple. So how can we create a tuple and name tuple? This is quite easy. There are two kinds of name tuples. The collections.name tuple class and typing.name tuple. And due to lack of time, we will only look into typing.name tuple right now. So this use a very easy syntax and allows us to specify the type of each field and it makes it easy to add methods to the class. An implementation of the pet class as a name tuple would look as follows. Our fields stay the same. Each pet has a name and a type and these are both strings. And when implementing typing.name tuple, we actually get some methods for free like we can print instances and get a nice representation of them. We can now access the fields by using either field names or the indices, which is especially helpful and it makes our code more readable. So let's think about our magical universe. We don't want our pupils, professors or ghosts to be immutable. For example, the pet of a pupil might change but a suitable group of people for an immutable class are the dark army members. And why is that the case? Because once you become a member of the dark army, there is actually no way back. So each dark army member will have a name and a birth year and we can further specify that master owner is the leader of the dark army. And now we can easily create members of the dark army. For example, Keras Fulford, who's one dreaded member and we can access the leader master odon either here on the Keras instance. So for example, what you can also see here is that you still cannot change attributes like the name and we made sure of this by seeing that we get an attribute error when we try to change Keras name into Mortimer. Okay, so we're nearly done. Quick summary, let's recap what we learned. Name tuples are an extension of plain tuples that present a shortcut for creating immutable classes but they are not the only way. So starting from Python 3.7, you can also use data classes for this but we don't have time left to talk about data classes. So what I want to give you for the end or when finishing this talk is that the topics that we now discussed are only a small portion of the features and topics that I worked on in the magical universe. So if you go and check out the GitHub repository or the corresponding blog posts, there are lots of more cool applications and Python features implemented using the universe like for example, data classes but also custom exception classes and lots of other things. And yeah, thanks a lot for listening. I hope you enjoyed our quick journey through this magical universe today. And the most important thing I want you to remember is that learning can actually be a lot of fun if you find a topic you're interested or passionate about like for me, Harry Potter but for you maybe something completely different. Thank you, Anna-Lina for that very fun talk. Although the time is up but there is a brief question that we can take. So let me just, what age are the students you're teaching these Python concepts to and do they have previous coding experience? Yeah, okay. So they don't need any previous coding experience. I guess what you're talking about now is this Kaim Marchule AI Go school. I talked about briefly in the beginning and there the kids at the moment are between the ninth and 12th grade in German schools but we are actually extending also our portfolio of courses to younger kids but then you need a completely different approach since for AI you need some mathematical background knowledge and that's what we are still using at the moment but yeah, you can definitely participate in the courses if you don't have any coding experience or knowledge about AI and coding. All right, that's very exciting and thank you for the great talk. You can connect with the audience in the breakout of Diverse Room. Definitely, I will do that. See you there, thanks. See you.