I come from a C Programming background, where for loops are very simple.
for(int i = 0; i < n; i++) {
// Your fabulous code block
}
This was intuitively easy to understand.
The first part
int i = 0is the initialization (the starting point) for the iterator. It is also nice that this int lives and dies within the scope of this for loop (block-scoped).The second part
i < nis the condition (test expression) that is checked for every iteration, before our fabulous code block gets executed.The third part
i++is the iteration (update) that happens at the end of every execution of our code block.
Now, if you are a pure-blooded Pythonista and you think that that loop definition is cryptic, please hear me out. With this simple setup, C can do a lot of funky loops, like:
for(int i = 1; i < n; i = i * 2)
// Oh look, an O(log n) loop!
for(int i = 2; i < n; i = i * i)
// Holy Hulk! An even better O(log log n) loop!
You get the drift. Fundamentally, every such for loop is easy to decode and understand. All good!
But when it came to Python, I was lost.
# A typical python for loop
for i in range(10):
Uhhhh, hello? Where does it start? What is checked? Who increments what?
Turns out, it was all syntactic sugar.
[It is technically more than just sugar.]
The definition is, behind the scenes:
for i in range(0, 10, 1):
# First argument 0 is inclusive
# Second argument 10 is non-inclusive
# Third argument is the step
range(10) == range(0, 10, 1) # -> True
range(10) == range(0, 10) # -> True
Okay, but what exactly is the 'range' here?
It just forms an object of type 'range' (since Python 3.0) which is nothing but a placeholder that signals where the loop starts, ends and increments. Technically, it is an immutable sequence object that represents the numbers in the range without actually storing all of them in memory.
Creating a range object takes O(1) space and time, regardless of how large the range is.
All those book-ish definitions are fine. But how do we better visualize it? Convert it to a list!
# Python REPL
# The default iterator increment is always 1
>>> range(10) # -> range(0, 10)
>>> range(10) == range(0, 10, 1) # -> True
>>> list(range(0, 10, 1)) # -> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Single digit odd numbers
>>> list(range(1, 10, 2)) # -> [1, 3, 5, 7, 9]
# Single digit even numbers
>>> list(range(0, 10, 2)) # -> [0, 2, 4, 6, 8]
Now we get it! We can extend this concept to scale and even to decrement instead of incrementing
>>> list(range(10, 0, -1)) # -> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
And of course, Python's for..in loop directly iterates over collections like list, tuple, dict or str, visiting each item in turn (or each key, in the case of a dict).
Splendid! But where are the O(log n) for loops?
Ah, that is where the "loop-hole" of Python lies. This can be done easier with a humble while loop.
i = 1
while i <= n:
# Your awesome code!
i *= 2
A for loop version exists but it is way trickier and beyond the scope of this post.
There, now your for-loop-in-range understanding is rock solid :)
This post is not to diss Python, not at all. In fact, Python's for loops have more capabilities than C's but I got to grok it only when I properly learnt the range function.
I will be happy to hear from you about any more such silly quirks of Python compared to C or other languages, in the comments.

Top comments (0)