 I want to talk about iter tools today as the module of the month. Part one is really cool. It's lead up. You won't understand part two if you don't catch part one. There's a cliffhanger and whatnot. I had a very good developer I used to work with who said you're not writing real Python code until you import the iter tools. Only then do you have a really proper Python module. And iter tools is great. Who here uses generators, yield statements on a general basis? Yeah, yield statements. And using iter tools, I think personally, will take you from a sort of beginner intermediate phase in Python programming to an intermediate advance. If you can understand the full range of tools, oh god, that iter tools offers you, it'll make you a way better developer and much less likely to write your own crappy implementations of what iter tools offers you, which will be bug-filled and problematic, as I have in the past. I just want to slowly go through a couple of things in this toolbox, the really basic ones. So all of these, as the iter tools thing hints to, they're all generators. So they don't throw lists or tuples, which you might have seen in maybe really basic Python. They throw what's called generators, which are basically it's like a list. It looks like a sequence. You can call a for loop through it. But they don't generate everything in memory at the same time. They don't generate what they need. And classically, a lot of stuff will throw infinite sequences. For example, count. Count will take a number. Or if you don't give it an argument, it'll start at 0. And throw an infinite sequence of numbers starting from whatever step you give. In this case, it's a step of 1, because that's in the argument. Cycle, you put in an iterator. So this string is an iterator of four characters. It'll just cycle through those four characters onto infinity. And repeat. Well, repeat, here I put a three to stop it. It'll spit out that first argument a number of times you say it. But again, these are infinite sequences, so you're thinking there'd be infinite amount of memory. No, it only spits out until you ask for it. Until you call, it used to be the next method on iterators. In Python 3, it's the next function. You put your iterator in it. And at each time, it'll throw out a new one. So these are like the basic, really handy, oh, I kind of do need a sequence. Range doesn't do it, because range is capped at some point. So I'm just going to use one of these. Corner case is kind of handy. It's when you combine it with other stuff. I slice. I found I slice really handy. It's like the square bracketed slice operators on lists, where you can specify I want the first element in a list. Well, I slice will let you do that on iterables. Because before, you have to call a list on the thing, which takes up a huge amount of memory and is really ugly. In I-slite, you can do it on any iterable you want. And the key is, it doesn't fill up in memory. So I could take an infinite sequence and ask for the millionth item to the 2 millionth item, or the millionth item to the millionth and 10th item. And it'll take a really long time, because I've got to do a million times around that. But it won't blow up your stack, your heap. It won't take up all the space in your memory. Because you're throwing away all the results you don't need by I-slice. So you can give a stop, a start, a stop, give it a step. So with any sort of iterable, it's great. I-zip. I-zip is like the basic zip function in Python. But if you play around in Python 2 with the zip function, you realize it'll try to empty out your iterable and create a list before zipping it together. What basically zip does is you give a series of lists, or a series of iterables in this case. And it'll take the first element of the first iterable. The first element of the second iterable, and the first element of the first iterable, et cetera, and stick them together into tuples like this. So let's say you have a list of, I don't know, names. And in the same order, a list of last names. Well, you can zip them together to have first name, last name, come up as tuples. And you can iterate over that with a for loop. I-zip is like zip into, except it's iterable because it's in iter tools. So it will just do a process every element one at a time. In Python 3, your basic zip function that comes out of the box now spits out an iterable. So you don't need I-zip anymore, your basic zip functions. A lot of functions in Python 3, like range, have been turned into iterables instead of physical lists. So it's a bit harder to grasp if you're playing around with it in the shell. But if you use it on a day-to-day basis, well, it's a lot less memory hungry, and sometimes faster, depending on users. However, you still get I-zip in Python 2. It's I-zip longest. In Python 3, it's zip longest. So zip will stop. If you get two lists of an equal length, it'll stop at the shortest one because it doesn't know what to do afterwards. I-zip longest, by default, it'll give none. It'll take the longest list, and the shortest list, it'll continue, and pass whatever the shortest list stops. It'll throw out none. Or you can give it whatever you want. So if you need to fill out two lists of an equal time, and my time's over. And the rest of it I'll cover in part two. Thank you very much.