Understanding iteration in Python
Iteration is a fundamental concept in programming where an operation or set of instructions is repeated multiple times. Many solutions to programming problems involve this repetition, either performing the same operation repeatedly or applying it to each element in a sequence. For example, if you wanted to get the square of each number in a list of numbers, you would use iteration to go through the list and multiply each number by itself. Another example is the ls
command available on Linux and MacOS terminals, which lists all the files in a given directory.
Prerequisites
A beginner+ level of understanding of the Python language. I will be using f-strings. If not familiar with them, acquaint yourself here.
Iterables and iterators
The main idea of the Iterator pattern is to extract the traversal behavior of a collection into a separate object called an iterator - refactoring.guru
The iterable stores the items, the iterator provides the mechanics of getting items from the iterable. This allows us to fetch data without putting the entire collection in memory. This separation of concerns (storage vs. access) enables lazy evaluation, which can be crucial for memory efficiency when dealing with large datasets or infinite sequences. Another benefit of this separation is that we can perform multiple independent traversals of the same collection simultaneously. An iterable can also have different iterators, implementing different traversal methods for the same iterable (e.g., forward, backward, filtered).
-
Iterable: An object that implements the
__iter__
method. The__iter__
method instantiates an iterator. When Python wants to iterate on an iterable, it callsiter(iterable)
, which in turn calls the iterable's__iter__
method to obtain an iterator. Although there is a fall-back mechanism where Python checks if an object implements__getitem__
and creates an iterator that fetches items by index. However, if you want your object to be iterated over, implement__iter__
since explicit is better than implicit. Examples of iterables in the standard library arelist
,set
,dict
,str
. -
Iterator: Objects that have an
__iter__
method that returnsself
and a__next__
method that returns the next item in the series. If there is no next item, raiseStopIteration
exception. Examples of iterators in Python areenumerate
,zip
.
Ways to perform iterations in Python
Typically, we perform iteration until a specific condition is met. The condition could be exhausting the elements of a sequence, or a change in some state.
1. for
loop
To iterate over items in an iterable, we can use the for..in
syntax which gives us access to items of an iterable.
Consider this example where we want to print the list of names and greetings.
If we would also like to access elements by index, we can incorporate the range
function like so for ... in range(start, stop, step)
. Python's range()
function generates a sequence of numbers from start
to stop-1
, incremented by step
. If start
is not provided, Python defaults this to 0
. If step
is not provided, Python defaults to incrementing numbers by 1
.
Let's print the names together with the indices
We can also use enumerate
to get a tuple of index and element. Let's reimplement the previous example.
2. while
loop
While condition
evaluates to True
or truthy, execute the indented code.
Let's count up to 5.
3. for...in...else
We can also add else
to the end of a for-loop, and this is run after the loop gets to the end (did not error out or encounter a break or a return statement).
Look at this example that searches for a name in a list of names. If we find the name, we print a message saying we found the name. If we traverse the entire list of names and don't find our search term, we print a message informing the user that the given query was not found. 'Barrack'
is not in the list of names, so let's use so as to ensure the code in the else
clause runs.
4. Comprehensions
List
Introduced by PEP 202, A list comprehension creates a new list and can be used in place of a for-loop.
The syntax of a list comprehension follows the pattern:
- expression: defines each element in the new list
- item: variable representing each element in the iterable
- iterable: source sequence
- if condition: optional filter
Consider this for-loop that counts the number of letters in each word and stores the result in a new list, counts
:
We can rewrite the example as a short and sweet list comprehension:
Set
The syntax resembles that of a list comprehension although it creates a new set (unique elements) and uses curly braces { }
instead of square brackets [ ]
Let's say we have a list storing people's favorite snacks and we would like to remove duplicates
Dict
Dict comprehension creates a new dictionary by applying the key and value expressions to each item in the iterable, optionally filtered by the condition.
- key: Defines each key in the new dict
- value: Defines each value in the new dict
Let's say we want to create a dict of word counts where key is the word and the value is the number of letters.
5. Generator expressions
Introduced by PEP 289, Generator expressions resemble list comprehensions syntax-wise but use brackets ( )
instead of square brackets[ ]
. Generator expressions create a generator, which can be iterated on, but are more memory-efficient than lists.
6. map, reduce, filter
These functions operate on iterables and help us perform an operation on elements of an iterable efficiently without writing a for-loop. map
allows us to transform each element in an iterable and return the result, reduce
combines all elements in an iterable into a single result by applying a given operation repeatedly to the elements, accumulating the result as it goes, filter
only "picks" elements that meet a certain condition from an iterable.
map
map
accepts a function, and one or more iterables, then creates an iterator that applies the provided function
on each element in the iterable(s) and returns the resulting element. We can even pass inbuilt functions as we will see below.
map(function, iterable)
We do not need to import map
to use it.
Let's say you have a list of words and you want to capitalize them.
filter
filter accepts a function and an iterable and makes an iterator that returns only the items in iterable
for which function
returns True
.
filter(function, iterable)
Let's use filter
to "filter" even numbers from a list of numbers ranging from 1 to 10
We can rewrite is_even
as a lambda function
We do not need to import filter
to use it.
We use the
lambda
keyword to create anonymous functions in python.lambda parameter: return_value
;lambda
being the keyword used to create an anonymous function,parameter
denotes the input to the function,return_value
is the expression that defines the return value.
reduce
reduce
is a function available in the functools
module and has to be imported.
reduce(function, iterable)
reduce
Applies a function to members of an iterable cummulatively from left to right and returns a single value. function
should accept two argument; an acummulated value and the next value from the iterable.
Conside this example that calculates the sum of a list of numbers. Note that Python has a builtin function called sum
that does what we are trying to do. We're just having fun.
we can rewrite calc_total
using reduce
Conclusion
Hopefully, we now understand python iterators, iterables, iteration, loops. We have also seen various ways we can rewrite loops or process iterables in a succint and efficient way. Happy iteration!