[MUSIC] [NOISE] In this video, I'm going to talk about generators. Generators are a way of programmatically producing a sequence of values in Python. What do I do with that sequence? Well, you iterate over it. Okay? In some ways, generators are like lists. Lists are a sequence of values and I can iterate over them, however the list exists in its entirety at once. And you can do other things besides iterate over them. Generators, on the other hand, do not, they, produce the entire sequence at once. Rather, they produce the sequence as you iterate over them. So, they're only producing one value at a time. This can be significantly more efficient, especially when you're producing large sequences of values. Alright, well generator's a little bit complicated, so I think they're best understood by example. So, let's take a look at some generators. So, here are a bunch of different examples of generators. And I want to start out by talking about list comprehensions to ground this in something that you probably already understand, okay? So, if I wanted to take the max of a sequence of values, I could write it like this where I take the max of a list comprehension. I have a list comprehension producing, you know, num times 2 minus 3 for all the numbers from 0 to 6, okay, and if I take the max of that, let's just run it, it produces 9, okay. And you'll notice I have max in list and max in gen. Well, I have a generator expression on line nine here. Okay, and it looks very similar to the list comprehension. The only thing is, I got rid of the brackets. Okay. Oh, okay, so that's no different right? Well, actually it's very different, okay. On line six with the list comprehension, the first thing that happens is the entire list of numbers get created. So I end up with a list with seven elements in it, then I pass that list to the max function. The max function iterates over it and finds the maximum number, okay? In the generator expression, I do not produce a sequence of seven values. Rather, I pass the generator to max. And as max iterates over the generator, each successive number in a sequence is produced. So, it still produces seven numbers, you still produce just as much. I just didn't produce it all at once in the beginning, to pass it to max, okay? And you can see I get the same answer, it's sort of functionally equivalent in this way. That if you're going to produce a sequence of values that you're just going to pass to another function that's going to consume them, right, there's no point in nes, necessarily making the list ahead of time? Okay? I can just use the generator expression, get rid of the brackets, type things exactly the same way, okay, and it's, you know, a little bit more efficient. For seven numbers, it probably doesn't matter. For 100,000 numbers, it would matter a lot, okay. Now, this is not the only way of building a generator, that's just a generator expression. Okay, I can actually build my own functions that are generators. So genfunc here is a generator function that I've written. And the big difference here is that instead of returning a value, this function yields values. So, notice the key word yield here. What does that mean? Well, when a generator gets to the yield keyword it returns that number but the function's not over, okay? So, it gives that number back to whoever called it. And that, you use that, and then, when you go to the next, sort of, iteration, it will come back right where that is, right where that left off. Right where that yield statement is. And, it will start continuing executing the code from there, remembering all the state of the function. Okay. So, let's just print out this function. We're used to calling the function and printing the result. Alright? Hm. Well, there is no result here, okay? When I define a generator function like this, Python looks and sees, hey, is the yield statement anywhere in this function. If so, it's a generator, okay? It does not return a value immediately, when you call it. Rather, it return a generator when you call it. So, I have to actually use this to iterate over. So, here's how I use it, okay? I have a loop. A for loop here that loops over the generator. Oops, I didn't mean to do that, sorry. 'Kay, and printing to number that it gets back. 'Kay, so this generator, well, it's kind of a silly generator, right? What is it? It's range, in some sense, okay? I'm basically saying, give me all the numbers from zero up to but not including limit, okay, and that's exactly what it's doing. And I can now iterate over it for number in genfunc seven, print number, it's exactly the same as if I had called for number in range seven. Now, I can also send this function, this generator function that I've defined, to any other function that's expecting a sequence. Okay. So, max is one of those functions. Okay, I can send my, use my generator function, instead of a generator expression. Okay, and you can see that it returns the right answer, right? I passed genfunc 4 to max, which is, you know, basically the same as calling range 4, right? It's going to generate the sequence, zero, one, two, three. Max is going to look at those, iterate over those, and find that three is the max. Okay. Now, there's a question of, what is the difference between yield and return? Okay? Yield means I'm yielding control voluntarily to, you know, you, while you're iterating. And I'm expecting to come back and pick up where I left off, right? Return says this function is done, I'm not coming back. Okay? So you can also have return in, in you generator function. Now, you can't return any values. You can only yield values from a generator. When you return, you're basically just ending the iteration. Okay, so you don't have, you just return. You don't return anything in particular, or you can't return anything in particular. You just have to have a return by itself. So genfunc two here, you can see that instead of giving me a number to tell me when to end, I'm still you know, generating number zero and incrementing by one, so zero, one, two, three and so on. But, now I'm going to take a function. I'm going to make this a higher order generator, so to speak. So, it's going to take a function and, when that function returns true on the number that I have, then, I'm going to return instead of yielding. Okay. So, this is basically an end condition. And, I have a simple end function here, where, if the number you pass it is seven, it returns true, otherwise, it's false. So, now, my generator, if I pass that particular end function, is like calling range seven. Alright, so let's look at this and see if it works. Okay. I'm iterating, iterating over this new generator function, genfunc 2, I'm passing it end to FN. And, I get the number of 0123456. So, that works, as well. Okay. So, this is critical to understand, that yield, yield's controlled temporarily, expecting to come back right where it left off. And, keeps all the state of the, the generator function and it just keeps going. Whereas, return says, this is the end and the iteration will now stop. So, generator expressions and generator functions are now one more tool in your toolbox. And they're both simple and complex at the same time, okay? A generator expression looks just like a list comprehension, but it's not, right? It doesn't ever produce the list, but you can use it in places where you're going to just iterate over the sequence. A generator function is a little bit harder to get your head around maybe, but you just use this yield statement wherever you need to return a value to the iterator, okay? But remember, you're going to come back right where you left off and just keep going. Okay? If you actually use your return, that means you're done. The iterator is going to end, okay. And so this a powerful way of producing a sequence of values programmatically. Instead of trying to buildup a list or a tuple ahead of time, I just build them on the fly. And whenever I'm only going to iterate over the sequence, this can be a very good option.