10 min to read
Python - Control Flows - Looping
This article is a part of the Python - 101 series, you can access the full version of the series here:
-
Coding 101
-
Writing functions in Python
-
Control flows in Python
- Looping (you are here!)
- Conditions
-
Errors and exceptions
-
Classes
Welcome to the seventh article of the Python - Coding 101 series, which is also the starting of the Control Flows section. After reading this article, you’ll learn:
- What is iterables and iteration, also the benefit of lazy evaluation
- What is list comprehension and generators and how to use them
Remember that we have covered the basic of control flows in the first article of the series? In this article, we will dig deeper into the intuition behind looping, together with some techniques to write more efficient code. The outline of this post is as follow
1. Intuition behind looping
1.1. Iterables & iterators
We have been working with loops for some time now, we have known the syntax of a for loop, and some special characters that can be used like break
, pass
, etc. But do you know that loop is built on the basis of iterators and iterables.
In Python, iterable is an object that can be looped over. For instance: lists, dictionaries, strings, etc. All iterables can be passed to the built-in iter
function to get an iterator from them.
>>> iter(['some', 'list'])
<list_iterator object at 0x7f227ad51128>
>>> iter({'some', 'set'})
<set_iterator object at 0x7f227ad32b40>
>>> iter('some string')
<str_iterator object at 0x7f227ad51240>
So what exactly is an iterator? You may ask. An iterator is an object representing a stream of data. It does the iterating over an iterable. You can use an iterator to get the next value or to loop over it. Once, you loop over an iterator, there are no more stream values, thus will result in an error if you want to access its next element.
>>> iterator = iter('hi')
>>> next(iterator)
'h'
>>> next(iterator)
'i'
>>> next(iterator)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
For and while loop all rely on iterables and iterators. You can understand that such loop is just the work of getting an iterator from an iterable and then repeatedly asking the iterator for the next item. So next time you look at a loop, remind that iterators are hiding behind the scenes.
Iterables and iterators are the core concept of lazy evaluation, which is a very helpful approach when working with big data.
1.2. Why do we need to know iterables & iterators
Iterators allow us to both work with and create lazy iterables that don’t do any work until we ask them for their next item. This is a very useful feature when working with a large amount of data. Because of their laziness, the iterators can help us to deal with infinitely long iterables. In some cases, we can’t even store all the information in the memory, so we can use an iterator which can give us the next item every time we ask it. Iterators can save us a lot of memory and CPU time.
This approach is called lazy evaluation.
2. List comprehension & Generators
2.1. List comprehension
2.1.1. What is list comprehension
Remember lambda function, with which we can declare a function in one line of code? With list comprehension, we can do the same with loop.
So list comprehension is just a better, more efficient version of for loop.
2.1.2. Why list comprehension
-
Strength:
- More computationally efficient than for loop
- Coding time and space efficient, since you can write it in only one line of code
-
Weakness:
- Have to sacrifice readability
- Can only be used to populate a list, where as for loop can work with any other data types
2.1.3. How to create list comprehension
Basic Syntax: [<output expression> for <iterator variable> in <iterable>]
Types of list comprehension:
-
Basic
# original list >>> nums = [2, 32, 12, 5] # for loop >>> new_nums = [] >>> for num in nums: ... new_nums.append(num + 1) >>> print(new_nums) [3, 33, 13, 6] # list comprehension >>> new_nums = [num + 1 for num in nums] >>> print(new_nums) [3, 33, 13, 6]
-
Nested
How about nested for loop? Can we convert it into a list comprehension? Check out the example below:
# for loop >>> pairs = [] >>> for num1 in range(2,4): ... for num2 in range(0,4): ... pairs.append((num1, num2)) >>> print(pairs) [(2, 0), (2, 1), (2, 2), (2, 3), (3, 0), (3, 1), (3, 2), (3, 3)] # list comprehension >>> pairs = [(num1, num2) for num1 in range(2,4) for num2 in range(0,4)] >>> print(pairs) [(2, 0), (2, 1), (2, 2), (2, 3), (3, 0), (3, 1), (3, 2), (3, 3)]
-
Conditions with list comprehension
List comprehension can also be used with conditions, expanding its use cases. You can use list comprehension with 2 types of conditions, on the iterables and on the output expression
-
Conditionals on the iterables
If you use conditionals on the iterables, the output list is filtered for only iterator variables that satisfy the conditions.
>>> nums = [2, 32, 12, 5] >>> [num ** 2 for num in nums if num % 2 == 0] [4, 1024, 144]
-
Conditionals on the output expression
If you use conditionals on the output expression, the output value is varied to satisfy the conditions.
>>> nums = [2, 32, 12, 5] >>> [num ** 2 if num % 2 == 0 else 0 for num in range(10)]
-
2.2. Generators
2.2.1. What is generators
Generator is like a list comprehension, except it does not store the list in the memory. Generator does not actually construct the list, but it is an object that we can iterate over to produce elements of the list as required.
2.2.2. Why generators
-
Strength:
- Every strength of list comprehension
- Lazy evaluation -> memory efficiency
-
Weakness:
- Have to sacrifice readability
- Every time you want to reuse the elements in a collection it must be regenerated
2.2.3. How to create a generator
To create a generator, use the same syntax as if you were creating a list comprehension, but change the double brackets to parentheses: (<output expression> for <iterator variable> in <iterable>)
# original list
>>> nums = [2, 32, 12, 5]
# create a generator
>>> new_nums = (num + 1 for num in nums)
# check its type
>>> print(type(new_nums))
<class "generator">
# create a list from a generator
>>> print(list(new_nums))
[3, 33, 13, 6]
# or print it sequentially
>>> new_nums = (num + 1 for num in nums)
>>> for i in list(new_nums):
... print(i)
3
33
13
6
You can also create a generator functions, which return a generator object when called. It is defined like a regular function (with def
), but change the output expression to yield
# define a generator function
>>> def num_sequence(n):
... """Generate values from 0 to n."""
... i = 0
... while i < n:
... yield i
... i += 1
# use it
>>> result = num_sequence(3)
>>> print(type(result))
<class "generator">
>>> for item in result:
... print(item)
0
1
2
Comments