Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Transforming and Generating Data

00:00 In the previous lesson, I went into detail about the iterator protocol and how you create iterator class objects. In this lesson, I’ll show you some examples of why you might do this in the real world.

00:12 One use of an iterator is to perform transformation operations on an interval. Your iterator takes an interval and performs an operation on each item in a sequence before returning it.

00:23 For example, say you wanted to iterate over a set of numbers and get each of their squares. An iterator could do this for you. Let’s look at some code for the next few examples.

00:36 I’m going to use a single file called ators.py SquareIterator is the first class in that file.

00:43 It’s similar to the Cat and Dog iterators in the previous lesson. Starting out by tracking the iterable and storing an index, and as all iterators are themselves iterables, it defines a __iter__ in this case returning itself.

01:00 __next__ is slightly different from the dog iterator. I’ve reversed the logic. I start by checking if the index is still valid and if it is I perform my data transformation, in this case, calculating the square of the item in the sequence.

01:16 Then like dog iterator, I increment the index and return the squared value.

01:23 If the code gets here, then the index is outside of the sequence, and so a StopIteration gets raised. I could also have done this with a try-except block, catching an IndexError and re-raising it as a StopIteration instead. I’ve never done a performance check on this.

01:38 I suspect in older Python the if clause will be slightly faster, but they did some optimization of try-except in Python 3.12, so the code might be slower now. Either way won’t be enough of a difference in speed to really worry about for code this short.

01:54 Let’s try this in the REPL.

01:58 Import the class,

02:04 create a list of numbers,

02:08 and then I’ll use my iterator class directly in a for loop

02:18 and the result is the squares of the original. For something as simple as squaring, you’re probably better off just doing it inside the for loop itself, but then you wouldn’t be learning about iterators.

02:28 The example is a bit simple, but hopefully you get the idea. If you were doing more complex data transformations than a single operation, building a custom iterator gives you reusable code that abstracts away that operation.

02:42 It’s nice and clean and relatively easy to understand to future programmers because it says exactly what it’s going to do.

02:50 Iterators can also be used to abstract away the creation of data. Instead of transforming a sequence, it’s responsible for creating one. An example would be calculating the values in the Fibonacci sequence.

03:03 Let’s go look at some code that does just that.

03:07 I’m back in ators.py this time showing you the new FibonacciIterator class. The Fibonacci sequence goes on to infinity, which is a great way of having your program run forever.

03:20 That’s not true. Eventually, you’d run into limitations, but as Python 3 doesn’t actually have an integer limit, I’d be interested to see what would fall over first.

03:29 Anyhow, to prevent infinity, this iterator has a stop condition which defaults to generating ten values. So here I’m tracking both the stop condition and the index of the value being returned.

03:42 The current value of a Fibonacci number is the sum of the previous two numbers. Here I’m setting the base values for the calculation. Depending on whose Fibonacci definition you’re using, you might see one and one instead of zero and one.

03:55 Your mileage may vary you end up in the same place. Inside __next__ you have to check whether or not it’s time to stop. If it isn’t, then return the current Fibonacci value and calculate the new values for the current and next. Let me try this out.

04:16 Importing, creating an instance object in the for loop directly.

04:27 And there you go. You’ve got some Fibonacci on your chin. Master sauce always gets everywhere. That’s why you tuck the napkin into your collar.

04:36 Iterators have a bit of boilerplate code in them, but you can skip a tiny bit of it by inheriting from the iterator class in the `collections. abc module. abc` in this case stands for abstract base class.

04:50 The iterator base class implements the __iter__() method for you. Of course, that’s only two lines of code, but inheriting from an iterator kind of shouts, “Hey, this class is an iterator,” so there’s some value even if it only saves you a couple of lines.

05:05 The base class also uses another method called __subclasshook__. This performs validation on classes that subclass from the parent. In this case, it makes sure you’ve implemented __next__, and if you didn’t, you’ll get an error when you instantiate the class.

05:22 You can also do iteration in Python with generators.

Become a Member to join the conversation.