Sign in to your Python Morsels account to save your screencast settings.
Don't have an account yet? Sign up here.
How you can make your own iterator objects in Python?
iter and nextAn iterable is an object that can be passed to the built-in iter function to get an iterator from it:
>>> numbers = [2, 1, 3, 4]
>>> iterator = iter(numbers)
And an iterator can then be passed to the built-in next function to get its next item:
>>> next(iterator)
2
>>> next(iterator)
1
But iterators are also iterables, which means you can pass them to the built-in iter function and they'll give you themselves back:
>>> iterator
<list_iterator object at 0x7fbb2a806ce0>
>>> iter(iterator)
<list_iterator object at 0x7fbb2a806ce0>
Let's make a class whose objects are iterators.
We'll make a CountDown class which counts downward from a given number to zero.
Here's the start of this class:
class CountDown:
"""Iterator which counts downward from a given number to 0."""
def __init__(self, start):
self.n = start
To make our class act as an iterator, we need to support the built-in next function and the built-in iter function.
next function relies on __next__The built-in next function relies on the __next__ method, so we need to implement a __next__ method.
Our __next__ method should count downward and also returning the given number:
def __next__(self):
number = self.n
self.n -= 1
return number
This __next__ method gets the current number, decrement its, and then returns the original number.
But right now, if we were to call next on this object, we would decrement downward forever.
We need to raise a StopIteration exception when we reach 0:
class CountDown:
"""Iterator which counts downward from a given number to 0."""
def __init__(self, start):
self.n = start
def __next__(self):
if self.n <= 0:
raise StopIteration("0 reached")
number = self.n
self.n -= 1
return number
This CountDown class makes objects that work with the __next__ method:
>>> counter = CountDown(3)
>>> next(counter)
3
>>> next(counter)
2
>>> next(counter)
1
And this object hits 0, it'll raise a StopIteration exception:
>>> next(counter)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/trey/countdown.py", line 9, in __next__
StopIteration: 0 reached
But this class does not yet make fully functional iterators.
If we try to loop over this object, we would get an error:
>>> list(counter)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'CountDown' object is not iterable
All iterators should also be iterables.
And our CountDown objects are not iterables yet.
They're not iterables because they don't work with the built-in iter function.
iter function relies on __iter__To work with the built-in iter function, our objects need a __iter__ method.
For iterators, the __iter__ method should return self, because __iter__ returns an iterator and iterators are already iterators:
class CountDown:
"""Iterator which counts downward from a given number to 0."""
def __init__(self, start):
self.n = start
def __next__(self):
if self.n <= 0:
raise StopIteration("0 reached")
number = self.n
self.n -= 1
return number
def __iter__(self):
return self
Now our CountDown class makes CountDown objects which work with a built-in next function:
>>> counter = CountDown(3)
>>> next(counter)
3
And which can also be looped over:
>>> list(counter)
[2, 1]
These objects are fully functional iterators now.
But... we probably shouldn't have made this class because there's an easier way to make an iterator.
Iterator classes are very rare to see.
The easiest way to make an iterator in Python is to make a generator object, either by using a generator expression or generator function.
This count_down generator function does basically the same thing that our CountDown iterator class did before:
def count_down(start):
"""Iterator which counts downward from a given number to 0."""
n = start
while n > 0:
yield n
n -= 1
We can take the generator objects that count_down returns:
>>> counter = count_down(3)
>>> counter
<generator object count_down at 0x7fcf71c64190>
And pass them to the built-in next function.
>>> next(counter)
3
Or we could loop over them:
>>> list(counter)
[2, 1]
This generator function does work a little bit differently, though.
Our CountDown class from before actually exposed an attribute called n:
>>> counter = CountDown(3)
>>> next(counter)
3
>>> counter.n
2
And we could change that attribute to change where our iterator continued counting from:
>>> counter.n = 5
>>> next(counter)
5
Whereas, our generator object doesn't expose any attributes. And if we try to assign an attribute to it, we would get an error:
>>> counter = count_down(3)
>>> next(counter)
3
>>> counter.n = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'generator' object has no
attribute 'n'
Iterator classes are pretty much only used if you need to make an iterator object that has other functionality beyond just iteration.
File objects are an example of an iterator with other functionality.
Files are iterators, so you can pass file objects to next, or you can loop over them:
>>> f = open("my_file.txt")
>>> next(f)
'This is line 1 in the file\n'
But you could also seek within files. Or close files:
>>> f.close()
Or check to see whether the file is closed:
>>> f.closed
True
Also, csv.reader returns iterator objects.
You can csv.reader objects to next:
>>> import csv
>>> reader = csv.reader(open("au-states.csv"))
>>> next(reader)
['State', 'Capital']
And of course you can loop over them.
But you can also check what line number you're currently on with the line_num attribute:
>>> reader.line_num
1
The most common way to make an iterator is to make a generator by making a generator expression or a generator function.
But if you need to make an iterator that does more than just iteration (that has additional functionality) you could make an iterator class with a __next__ method and a __iter__ method.
We don't learn by reading or watching. We learn by doing. That means writing Python code.
Practice this topic by working on these related Python exercises.
Generator functions look like regular functions but they have one or more yield statements within them. Unlike regular functions, the code within a generator function isn't run when you call it! Calling a generator function returns a generator object, which is a lazy iterable.
To track your progress on this Python Morsels topic trail, sign in or sign up.
Sign in to your Python Morsels account to track your progress.
Don't have an account yet? Sign up here.