 Our next presenter is Victor Terron. He's a reliability engineer at Google, and he will present us Kung Fu Add-Down with Eiter tools. Well done. Thank you. And this is not Kung Fu, but it was a super cool image, so I had to use it. We will make up for it later. Also, I have a lot of things to say. If any of you disagrees, please raise your hand and disagree. We all know how we loop over a list in Python, right? We are told this all the time. We have to say four things in whatever do thing. For example, here, we're looping over the elements. And we are always told, that's way better than doing this, which we might be tempted to do when we come from other languages. We could say, OK, I'm going to generate all the indexes, and I am going to do stuff with the indexes, accessing each one of the elements. And even worse, we know we don't want to do this. Like, even instead of using range, we could try to increase the value of index by hand. We don't do this. So we know. We have to use what it's Python for loop, which in other languages is a for edge. And we know that that's going to give us a higher instruction level, which means the code is more readable. It's clearer. So we like it. We don't have to think about what's going on. We only say, OK, loop over this, whatever it is. And it works. It's nice. Also, it's safer. And this is important, because we are not working with index. So we don't have to think that's a major goal in life. We just have to do stuff. For example, if we tried to work with indexes again, it could. This is an off-way one error. We forgot. We went too far. And we tried to access the last element. It's not there. When we use a for each, a Python for loop, this is not something that can happen. And of course, it's not only going to work with lists. We can use it, for example, with strings for each letter in the string. Well, letter is a name of the variable. We can access each one of the characters in the string. We can also use it with sets. We can loop over the elements, although we don't know the order in which we are going to see them. We can loop over dictionaries. For example, here, using items, we can get the key and the value of each of the mappings. And even with files, we open a file. And then we use the loop over the file descriptor. And we can loop over each one of the lines. And that's the Python way. In the end, we can use loop with anything that's iterable. And that's in the docs. We take it for granted. Now the question, what's an iterable? When we go to the docs, it turns out all the answers are not only in stock workflow. You can also go to the docs and read them. And it says, OK, an iterable, it's not yet capable of turning one element at a time. Blah, blah, blah, blah. It means our class has to implement a magic method, dunder, iter, dunder, or also get items that's focused on the first. So OK, we have to implement either this magic method, what's this magic method? And it says, OK, this method is going to be called when we need an iterator. And it should return a new iterator. So we know we need to implement either and make it return an iterator. So first of all, at some point, we will call either the built-in. And we will get the iterator. And it's true. If we have a list of numbers, for example, we can say, OK, print dunder, iter. And yes, it's there. It's a method of the list object. And if we call either the built-in, we get the iterator. And yes, it's a list iterator object. So I'm not blind. But now, what's an iterator? Well, it turns out an iterator is something that conforms to the iterator protocol, which means you have to implement two different methods. You have to first implement either again. And you have to return self, which makes sense. But at first, it doesn't look like. But you have to do that thing. So this is, remember, we are writing our own iterator. And the docs are right. It makes sense, because in that case, you can use the iterator with a for and in statements. Any way, we don't care. We will do this. We can follow instructions. And then we have to implement another magic method. We have to implement next. And next should return the next element. And when there are no elements left, which it should raise, stop iteration. I don't know what the second sentence means. It's something with low level. And this was next in Python 2. But nobody uses Python 2 anymore. So now we have finally our clumsy and awkward iterator. And we are looping over a series of elements. And iter is going to return self. And next is going to return the next element. Once we are no more elements, we are going to raise stop iteration. Jay, we did it. And we can see once there are no elements left, stop iteration. Now, something important. We know that when we work with iterators, next is going to return the next one. But we can only use next with iterators. So if we try to call next directly on a list, it's not going to work. It's going to say, OK, no, it's not an iterator. You cannot do that. So we have to call first iter. And then we get the iterator. And then we can call next as many times as we want until we hit this stop iteration, which means no more elements left. And that's exactly what the for loop is doing under the hood. It's using iter. It's getting an iterator. And then it starts calling next forever until it sees a stop iteration. So if we want to implement the for loop in pure Python for no reason at all, we will have to do something like this. And that's basically what in low-level actual beautiful code it's happening in Cpython. Now, something nice. We said, OK, your iterator, this monster we wrote, that raises stop iteration, it has to raise. It's raising an exception when there are no elements left. But we are not seeing any exception when we work with Python and we loop over something. We are not going to see an exception. Why? Because the for loop is actually listening for this stop iteration. And that's the signal it uses to stop processing. So it's actually going to be expecting a stop iteration in one of the next calls. And when that happens, it breaks. Now, generators. Generators are a special type of iterator. We all know how a normal function works. We start in the function, we do stuff, and then at some point we finish and we return. And yes, that's obvious, but sometimes we don't think about it. Whatever we have to return from our function has to be done at once. For example here, we have a super simple function and it has a single exit point. So we do something in the first line, second line, and then we return a value. We can have multiple simple points. This is one on one. We can return the function either in the first return or using the second. But once we hit one of the returns, we are going to finish. And we will never be back. If our function doesn't have a return, that's an implicit no. So actually, Python functions are always returning something. If we don't say return something, it's returning no. Now, how is that the problem? Well, it's not. But for example, let's say we want to work with all the even numbers in the world. We want to get even function that it's going to give us a lot of even numbers until a threshold. We could write it the simple, the ugly way or the Python way using rage, which we cast to the list. And it works. That could be a common problem at some point. We want to work with a lot of numbers because this function, we said, OK, we have to return everything at the same time. So if I want to sum billions and billions, Carl Sagan would say of numbers, we can't do that because that's not going to fit into memory. So that's when we use generators. Because generators are functions that are able to return values one by one, which means the state of the function is frozen. And we don't use return. We use yield. And when we get to a yield, we return a value. And the function freezes there until we call the following next and over and over. That's super cool. And basically, if we have one or more yields in our function, it's not going to be a normal function. It's going to be a generator function. And it's going to return a generator, iterator, which for short, we refer to as generator. So for example here, simple generator isn't using return. It's using yield, which means we can call it up to three times. And it's going to return values one by one. The last time, there are no elements left. So it's going to return as all iterator and stop iteration. So in this case, we said, OK, we want to return all natural numbers. I'm going to do this. I have an endless loop. It's never going to work. But I don't care because I am only returning elements one by one. And in this way, I could keep calling this generator all the time. With a normal function, I urge you to try this. But if you try to run this, you will end up killing the process because it's never going to work. We can never return all natural numbers as far as I know at once. So PLDR, I wasn't paying attention. Iterators are objects on which we call next. That's the only thing we care. And every generator is an iterator, but not vice versa. Also, and this is something great. And Alex Mortelli wrote it once on Stack Overflow. Everything you can do with a generator, you could do it with your own iterator class. But generators give you a basic framework. Basically, all the keeping state is already done for you. The framework is there. So if it's a simple, most of the time, for simple cases, we can use a simple generator. For extra points, how do we measure a generator? Can we call lang? No, we can't because we don't know. Well, we don't generate numbers one by one. So if we call lang, it's going to say type error. You can do that. We could cast it to a list. And then we could call lang. What's the problem? As we said before, we are going to be building all the list into memory. Better, we can use some. We can say, OK, I'm going to add a lot of numbers. One for each element in the generator. So we have to exhaust the generator. But in that way, we can count it. Usually, for ask convention, we use a underscore as a throwaway variable to signal we don't care about the actual value. But then wait. Time for something full. Three or five minutes left. I wouldn't need some water if that were possible. If not, it's fine. Do we have water? We do have water. OK, that's the good life. Thank you. Prime numbers. Prime numbers are great. How do we determine if a number is prime? There are a lot of actual ways, really mathematical ways. But I like the simple stuff. I am going to do it by hand. I am going to try all possible numbers. Because a prime number is only going to have two divisors, right, one in itself. OK, I am going to try all the other numbers. And if there is at least one divisor, you are not prime. Otherwise, you are prime. So I am going to do it this way. It works. Now, we don't have to do that really ugly thing. Do we need it by hand? We said, no, no, no. Use range. And with range, we can loop elegantly over all the candidate divisors. This is a mandatory optimization. Although it's not strictly related to what we are seeing here, we only have to check divisors up to the square root. There are a lot of proofs. I didn't understand them, actually. Really? But stackable flow, again, there is a proof for humans there. But it basically says every non-prime number will have a divisor less than the square root or equal to. So OK, that's a lot of improvement. Done for now. So now, integer, how can we use that to something remotely useful, integer factorization? So we want to decompose a number into factors. So for example, 8 is 2 times 2 times 2. Simplest method, again, the trial division, which means we are going to try all the prime numbers. And we are going to, OK, let's see. OK, can you divide the number by this? So to write my solution here, I said, OK, I want to get the first n prime numbers. How do we do it? OK, I'm going to build an empty list. And I am going to keep trying numbers. OK, number, are you prime? Yes, then I put it into the list. And finally, I return it. It works. Again, problem, we are returning all the prime numbers at the same time. So OK, we are here. So let's use a generator. And in that way, it's actually the same. But we don't use these memory in list. We can return prime numbers as we generate them. But now, we are doing two different things here. And that's how our code can break. Because we are keeping track of how many numbers we have generated so far. And what's the next number we have to evaluate? Oops, oops. And that's Iterotools count. Because we are in the Iterotools talk. Iterotools count, it's super simple but cute. It's going to make an iterator that returns numbers. By default, we are going to count from 0. And the default step is 1. In other words, it's like range, but with no upper limit. We can keep calling it forever. For example, here, OK, next, next, next. And we will get numbers. And how is this useful? Well, we can at least simplify our code a little bit. We can say, OK, for a number in Iterotools count. And that's never going to stop. But still, we are doing two things. We are generating prime numbers. And at the same time, we are counting. How many have we generated so far? So I'm going to do something. OK, I'm going to split it into two different things. First, a generator of prime numbers. And then, a function, getPrimes, which is going to be used in the other generator. And it's going to say, OK, how many have we returned so far? This many, OK. At some point, we have enough. We stop. Good. But we can make it even better because, OK, we have written a function there for primes. We can use a simple generator expression. Or arguably, maybe even better in this case, we can say, OK, OK, filter. I'm going to filter all the numbers starting from 2. And I am going to take only isPrime, because that's what the filter is going to do. Filter is going to, lazily, one by one. It's going to check all the numbers every time we call next. And it's going to return the next. That satisfies this condition. Filter, by the way, was either tools I filtered before. But again, that's old history. GetPrimes now becomes this thing. OK, I have the counter. And I have my list of primes, because then primes is going to be returning primes one by one. And we can count how many we need. But now, take how many. This thing is a common pattern, actually. Give me the first n elements of this interval. And it turns out we cannot use slice notation, because it's going to complain, actually. It's going to say, no, a generator is not subscriptable, which makes sense, because you are generating elements one by one. If you are doing that, how are you going to access randomly elements? You cannot do that. But you can use iter-iter-tools-is-slice, which is basically like slice notation, but doing lazily with all things iterable. So for example, if we have iter-tools count, which means all the numbers in the world, and we say, OK, iter-tools-is-slice, give it five. Take five. It's only going to return the first five. And then stop. But we can use it not only with generators. We can use it with, for example, with a string. And we are going to get the first three letters, and yes, as usual, we can use steps other than one. Or we can start counting not from zero, but from other value. So if we want the first n prime numbers, we can say, OK, iter-tools-is-slice, primes, and as many numbers as we need them. So let's rewrite our function. Now we get, OK, I have all the prime numbers in the world. I am going to loop over this thing, is slice is returning, and I am yielding them. And now that's OK. We are looping over an iterator. And the only thing we do is yielding them. That's what yield from was invented from. Gield from has, well, it allows us to do other stuff but the important part. It allows us to delegate part of the work to a different generator, which means every time we call next, yield from is going to ask the second generator, OK, in turn, next, give me your value. And it's going to return it. So it's basically the same thing as we were doing before. It's the same thing as doing for n in thing, yield, that thing. We can do it within a single line with yield from. So here, spam is using yield from foo, which means we are simply calling in turn foo and returning the element. So it's called to spam, goes to foo, and foo yields an element, and spam yields it in turn. And it works. Again, not only with generators, we can use it with anything that's iterable. For example, here, we are yielding from a string, yielding bubbles. So we can rewrite our function as this. We are simply, OK, we are taking a list of primes, and we are taking how many did you say you need, and it works. For example, in this case, five, and it works. 18 minutes so far, and I have not answered the question because we are actually not going to need this. We don't want to get the first n prime numbers. We want to get a series of prime numbers up to the square root. That's what we said we have to do. So no. So we have to actually generate generators up to a value. That's the threshold. We don't know how many. We know the maximum value. So we could say, OK, I'm going to do it the naive way. I'm going to have these primes until function, and I am going to loop over the primes. At some point, I am going to hit the limit, and then I will stop. If the value is less than the threshold, I will yield this prime number. And it turns out this is also a common pattern. This thing about we're looping until a condition is no longer satisfied, and that's take while. Iter tools take while is going to make a literator that's going to return elements, and it's going to stop when the condition is no longer true. That's usually seen with lambda functions, but it's not mandatory. So for example, here, we say, OK, you have numbers, which is a lot of numbers. And I want to take numbers while lambda x, x less than 4. So I want to take numbers as long as they are less than 4. So the first time we call next on the iterator, it's going to say, OK, I got 1 because it's the first element. Is 1 less than 4? Yes. So I yield it. I return it. I return it. Sorry. The same thing is going to happen with 3, with 2, with 3, but not with 4, because 4 is no longer going to satisfy the condition, so it's going to stop iteration because there are no elements left. Again, we don't have to use lambda functions. If we had a function here, which happened to be smaller than 10, we could say, OK, take while where they predicate the condition is this function, and we are going to evaluate it for each value. So our kung fu version of the thing we needed is this. I am yielding from take while where take while, what's the condition? OK, that we are not yet there. We have not hit the threshold. And what about the opposite? We have it. It's drop while. Drop while, it's going to make an iterator, returning again elements, but it's going to ignore all the elements that make the condition true. At some point, the condition is not going to be true anymore, and then we start up to the end with the returning elements. So for example, here, we are ignoring numbers as long as they are less than 4. So the first time we call next, what's drop while going to see? It's going to see 1, but 1 is not less than 4, so we drop it. Same thing with 2, same thing with 3. At some point, we reach 4. 4 is not less than 4, so we have to return it. And then we go on for on and on until the range is over. So combining both things, we can write this cute function to get prime numbers within a range. We can use take while. That's going to return an iterator, and we can feed it. We can pass it along to drop while. And in that way, we can generate all the prime numbers between start and stop. And yes, we could specify different values, of course. By default, we will generate everything from 2 to infinite, but we could specify other values. How is this useful? Well, because finally, to answer this thing, we have the algorithm for integer factorization. And we don't really care, but we can glance over it. We have to loop over all the prime numbers. And for each prime number, we try to divide the number by the prime number. If we can divide it, the prime number is a factor. We save it. And we keep doing it recursively. So very quickly, we have 30 is 30 divisible by 2. It is. So 2 is a factor. What's the quotient? 15. So we start doing it recursively with 15. Then we start with the first prime number. It's 2. It's not divisible. We move on to the next. And so on. Eventually, we reach the base case, and we will be over. So here, we are using primes until our neat function using iter tools under the hood. And we could change. Every time we find a factor, we create a new list. And we concatenate it to the recursive call. And we can see that it works. Not that we are actually going to use in production, but it's there. More kung fu. Now, the unique command. I need water. Sometimes, we have used unique command. Usually, Stack Overflow tells us to use it. And you copy and paste without knowing what you're doing in your servers. Unique is going to remove duplicate elements in standard input, but only if they are together, which means when you are starting and you go to IRC free node and you ask, how do I remove duplicate lines in a file? This detail, you have to sort. And then you pipe it to unique or this thing sort. That's you, right? OK, I want to write it myself. Why not? So let's say we only work with strings. So I have to do something like this. I have to say, OK, I am going to loop over all the words of the string. I am going to keep track of the result. And every time I see a new letter, I have to say, OK, are you different than the last thing I saw? If it's different, I add it. If it's not, I ignore it. So we will have to do it. Maybe faster ways. This is a kind of simple way to do it. And eventually, we return the result. OK, here we can use group by. Group by is super cool, but it's also hard at first. I struggled to find examples that made sense. This is the best I could find. So you have to use and group by is going to group elements. It's going to make an iterator that's going to return, every time we call next, it's going to return it to element tuple. The key and the group. And what's a group? Well, a new group is going to start when the value of the key changes. The group objects themselves are an iterable thing. So here we have this string with repeated elements. We say, OK, group by. Tell me what's groups. And it tells you, OK, it's a group by object. Thank you, Python. So now we say, OK, next groups. And it's going to give me. OK, the first group, which is the first key and the first group. And I say, OK, what's the key? And it says it's a. And what's group? It's a group by object. Thank you again. But well, it's an iterable. So I will keep going next until something happens. And it turns out I can call it two times. And it says, OK, you got an a, you got an a. And then you got a stop duration. I don't like you. So I don't know what's happening. One of these cases where the docs have improved a lot and still are hard to newcomers. Thank you. It turns out the key is what the group contains. For example, a. And the group are the actual elements. So let's go to b. It's going to tell us, OK, the key is b. Because this group has a lot of b's. And the group itself is b. Because there was only one b. And the same with c. We have the key is c. Because in this group, we have a lot. We like c's. And the group is c, c, c. Because we have three c's. And this is super silly, right? How could we use this to something useful? Well, for example, here, we can write a loop form. Maybe it's simpler this way. We can say, OK, print the key and the group. And we can say, OK, first we saw a group that's a's. And it had two a's. Same with b and with c. We have three c's. OK? That's useful. Because for example, here, we wanted to remove duplicate elements if they are together. We can say, OK, I'm going to group by all the elements. And every time I meet, I find a new group. I'm going to take the key. I don't care about the group. Because I want to remove duplicate elements. So basically, each key of each group is going to mark the beginning of a new set of distinct characters. And we can do it like that, or even in a simple way. Something else, this is a common interview question. You can find it online a lot. I want a compression algorithm. I am going to take a string. And I'm going to use the counts. So if I have three a's, I am going to replace it with a three. How do we do it? Well, if you don't know the power of iter tools, your life is pain and misery and toils and blob. Especially because you are supposed, you're in an interview to come up with something like this. You have to keep track of what's the current group, what's the number of times you've seen this last thing. Every time you start seeing a new group, you have to restart your counter. You have not to forget that eventually you're going to exit the for loop, but you still have something else in your hands. So you have to append it at the end. OK, no higher. It turns out you can use iter tools. And you can say, OK, I am going to group elements. And every time I find a new group, I take the key. And how many times, what's the count? OK, I will count the groups. How many elements were there in the group? And if you do that, you change the key and the count of elements. It's already in the group. And you have your compression algorithm. Now, key A elements in the group AAA, that's still strange. And that's because by default, group B is using the equality. So A is equal to A, because A is equal to A. That's silly, again, right? But it turns out the same way we've sort or sorted, we can use the key function. We can use it with group B. And we can say, OK, let's transform each element before we compare it. And that's known as the key value for each element. How is this useful? OK, I want to group letters by length. I want to say, OK, I have a series of words. And I want blue and gray to go together, because they have all four elements. So if we don't know better, although this is still good, we can say, OK, I'm going to use a hash table, a dictionary Python. It's going to be a default dict. And I am going to say, OK, for each letter, I'm going to count the number of elements. And I am going to dot key in the dictionary. And I am going to append the word. OK, but we can also do it with group B. I can say, OK, I am going to sort the list by dot key. And I am going to group all the letters. And ta-da, each group is already going to have all the letters that have the same length. We could argue a lot. And I am willing to do that, whether this or the previous question was better. And probably the other is faster. I will argue that this is clearer, because it's basically, it's already in the name. It's grouping things by a criteria. In this case, the length. It's all there. Now, my favorite, mathematics. Run away. We have this function that I found online. And we can use numpy to build a polynomial. That's all the mathematics I know. And we can evaluate it. We can call the function. And, well, I am stuck here. I have to keep going. It looks like this. OK, it's something. I am going to say it's time versus something really, really important. And we are at work. And we are monitoring this thing. And I have a super cool teammate trying to do this. And we spent three weeks in one single line of code. So we said, OK, how do we do this? We want to trigger an alarm. We have to awake everybody. If at some point, we spent less than three seconds below zero. And here by hand, we are relaxed. We know the answer, right? It's true. Because between zero and three, there are at least three seconds there, where we were below the threshold. For simplicity, let's say, OK, I only care at exact integers. So we can evaluate it only there. So now the question is rephrased. Have we at some point been through, are there, let's say, type of there, are there three consecutive points with negative sign? You can do it the ugly way. The beautiful way is like this. I am going to use not the equality thing for the grouping. I am going to say, OK, I want to group values by whether they are negative or not. And that's the key we use. The key is, OK, RQ less than zero. In this way, every time, what's the key going to be? Well, the key is going to be the key value for each element. So the key is going to be negative, true or false, which is going to say, OK, you are negative or not. And the group is going to have the actual values. So if we loop over it, we can see that the first group, the key is false because all the elements in this group, according to these criteria, are positive. So the condition is the key is false. And those are the four elements. But the second group, it turns out it's negative. That's the key. And we had at least three. So yes, the condition was true. We have to trigger an alarm. You can implement it without item tools. I would not do that. Something simple. We want to alternate indefinitely between numbers, between minus 1 and 1. If we are absolute newcomers, how do we do it? We write a while true loop, and we do that thing. If I got a minus 1, I switch to a 1, and the other way around. A little less beginner version would be, OK, I am going to multiply by minus 1 because somebody told me that's going to give me the opposite. So it's simpler. But still, we're probably at least the first front row. It's going to die if we try to do this because it's never going to end. So we have to use the pro version. And what is the pro version? Well, I was paying attention. It's going to be something enter tools, right? Yes. It's a generator. And yes, we could write a generator. We could say, OK, while true. That while true is never going to end. But I don't care because I'm going to be using the iterator. So every time I call next, I'm going to get the next element. But we have the conflux version. The conflux version is cycle. Cycle, it's going to take an iterable, and it's going to return elements one by one, and then it's going to start over. So we are going to loop endlessly over this input iterable. And this is nice. We are keeping a copy of the returned elements, which means if we were using a generator as the input, the generator event is going to run out of elements, sorry. But cycle saved a copy, so we can keep using it forever. As we can see here, G was a generator expression. At some point, it ran out of stuff because we cycled over it, getting 1 for 9, 1 for 9 all the time. So if we try to use G again, it says stop iteration. You have nothing else. But the cycle with other tools is still working. Now, if we want to iterate over two or more iterables, how do we do it? Because let's say I want to go from 1 to 10, and then I want to go down to two, and keep doing that forever. Because I am playing Kerbal Space Program, and I never get to launch my rockets. I want to do that thing. I want to cycle over two things. Over the first list from 1 to 10, and then the second. But Python is going to complain because cycle only takes one element. And by extension, how do we do it? If we want to loop over two different iterables, what do we do? For example, here, we want to say 1 to 3, and then all the vowels. Do we do this? Like, do we have to write two different for loops? Well, we have chain. Chain is going to take a lot of things as input, and it's going to make an iterator. And this iterator, this is super simple. It's going to return elements from the first iterator, then it's going to move to the second, and so on. So for example, here, we only have to say, OK, let's chain two different iterables. The first list and the second list, and it works. And then, in turn, we can pass this along to cycle. More conflu. And we are five minutes. That's at least 50 slides. OK, rolling dice. If we want to roll, if we roll to six side of the dices, that is, two normal dices, how many unique combinations are there? Oh, I remember that from high school. That's the Cartesian product, right? So I'm going to do a nested for loop, and then they tell you, OK, and if you have three, and you say, OK, I will write three for loops. But at some point, you have to generalize your solution. And this is really hard for at first, because, OK, how do I do it? Well, you can do it in different ways, but you can use recursion. And you can say, OK, for each element, I am going to combine it with all these sub-products. You create this ugly thing, which I can prove it works. But you have product, and product is super simple. Product is going to make an iterator, returning the Cartesian product of all the input iterables. If you want to compute the product of something with itself, you use repeat. For example, here, we want to roll two dices. We use repeat two. And we can see that the first 10 things it's returning are those. So an easy question. If we roll four dices, how many outcomes? In how many outcomes are they going to add up to five? Well, that's simple. I am going to use product. I am going to generate all the combinations. I know, but I don't want mathematics. And I am going to filter all the results. And I am going to take all that satisfy a condition. What's the condition that the sum of all these sites equals five? And well, it turns out there are only four different results. And finally, pixels. Let's say we're working with pixels and lots of them. This is something we see a lot. We have a constructor. We have this init function. And we say, OK, row and column. And the constructor is only saying, OK, row equals row, column equals column. And then we have the magic method, because we want to be able to print. We see this a lot. But this is a perfect example where we can use named tuple. Because named tuple is this wonderful factory function in the collections module, which is going to create a new subclass of a tuple for us. Which means if our class can be unmutable, it's going to make our life way easier. Because we only have to say, OK, I am going to create a new class called pixel, which has two fields, row and column. And that's going to do all the magic under here. And it's going to create the new class, pixel. And with that object in the new class, we can create new objects. And we get a lot of stuff for free. If we need functionality, we can use inheritance. We inherit from this class. And we can add our own method. For example, here, the distance. I want to be able to compute the distance. And now, all this was an excuse, because I wanted to say, OK, I want to get all the neighbors. If I have a pixel, I want to be able to move up, down, left, right, and to the four corners. I want to get all the eight neighbors of my pixel. I'm cool. I mean, this is intermediate level. So I know I have to use a generator. I am going to use yield, right? So I am going to do this thing. I am going to yield eight times. And I am going to return, well, eight neighbors. Yes, but it's a lot of lines for nothing, right? So we can do this thing. I am going to, instead of working with creating all my objects manually, I am going to use offsets. I am going to say, OK, at all times, I am going to move either 0, minus 1, or 1 in both axes. So using that, I can create the offsets and I can return all the neighbors. Of course, when we get 0 and 0 as a product, that's the pixel itself, where we already are. So we don't have to use it. We ignore it. In all the other cases, we can create the object. For super extra points, that's A plus, instead of hard coding the class name there, same yield pixel, we can not do that. And we can say, OK, type self. Have you ever seen that? If I do type self, I can CLS. For example, you could use any other name. It's going to be a new class. Sorry, it's going to be the type of our class. And then we can use it to create the new objects. In that way, if at some point we rename our class, we don't have to track down these things. And it works. So we have covered a lot of stuff. I didn't actually believe. I will be able to. It's a lot of cool things that are there in Python, waiting for us. But still, we have only scratched this surface. There are a lot of things in the IterTools module. And even more important, all these things that are so interesting, well, if they are for you, all the building blocks in the IterTools module are inspired in language like Haskell. So we have been saying, oh, IterTools is so cool. When, actually, we were doing functional programming. So maybe it's something worth looking into if you haven't yet. Because that's what we were doing here. And if there are any questions, I will happily take them. Thank you. OK. Thank you very much. We have one minute from one question. Yeah. Well, great, thank you very much. Do you see these constructs as a trade-off between readability and elegance or something like that? Or for you, this is? Sorry, the last sentence, a trade-off between what? Between readability of the code and the elegance of the brevity of the code. Because for some people to understand this, they have to know about these high-level constructs and understand what they mean. I don't know if you understand my point. Have you? I was thinking last night about this quote, about how somebody said, and it's a shame. I don't remember the name. One person said, if a new programming language doesn't change the way you think, it's worthless. So this, at first, it's super ugly. The group buy, I agree, it's super intimidating. Because what's happening here? At some point, you start seeing the patterns. And it turns out these are constructs that are happening a lot. So I would agree that for newcomers from the outside, this may make not a lot of sense. But once you change your mindset, I think there are problems that you simplify a lot. It's silly, from 100 lines to only two. So in some cases, thank you. OK, thank you very much.