Loop Related Tips

Learn how loop enhancements can improve code efficiency.

Embrace comprehensions

In addition to the well-known list comprehension, Python has many other comprehension expressions…

Set comprehension

Set comprehension is enclosed in curly braces {} and acts almost like list comprehension, except that the result is a set with the duplicates removed. We could accomplish the same effect by applying set() to list comprehension, but set comprehension is considerably faster:

Press + to interact
print({c for c in 'Mary had a little lamb' if c in string.ascii_letters})

Note: Remember that Python sets aren’t ordered. The printout order is somewhat arbitrary and doesn’t match the order of insertion into the set.

Dictionary comprehension

Dictionary comprehension constructs a dictionary. It uses curly braces {} as well and separates the new dictionary keys and values with a colon :.

The following expression creates a dictionary of human-readable character positions as keys and their values, but only for the alphabetic characters:

Press + to interact
s='Mary had a little lamb'
print({pos+1 : c for pos, c in enumerate(s) if c in string.ascii_letters})

So, we tried square brackets (list comprehensions) and curly braces (set and dictionary comprehensions). How about the parentheses? Wouldn’t we get a tuple comprehension?

Press + to interact
tc = (c for c in "Mary had a little lamb" if c in string.ascii_letters)
print(tc)

Well, no. The result is a list generator expression; a lazy form of list comprehension.

We can convert it to a list by applying list(), using it as an iterable in a for loop, or pass it as a parameter to another function:

Press + to interact
tc = (c for c in "Mary had a little lamb" if c in string.ascii_letters)
print(''.join(tc)) # Printing without space
print(' '.join(tc)) # Printing with spaces after every character

List generator expressions are often significantly slower than list comprehensions because of their underlying mechanisms. Yet, for huge lists, they give us the option of not creating an intermediate result in full before feeding it to another function.

Avoid range() in loops

The well-known built-in function, range(), that they are likely taught to use in a for loop, is often unnecessary. It is a tribute to C, C++, and Java, Python’s predecessors that need an index loop variable. Python doesn’t, and here’s why.

The classic non-destructive for loop over an iterable seq looks like this:

Press + to interact
#seq=[5,6,7,8,9]
for i in range(len(seq)):
do_something_with(seq[i])

Let’s print the seq using range().

Press + to interact
for i in range(len(seq)):
print(seq[i])

The variable, i, is used only to access the next element from the iterable. Because Python’s for loop is a for [each] loop, there’s a better way to accomplish this by iterating directly over the iterable (thus, the name):

Press + to interact
for item in seq:
do_something_with(item)

Let’s print the items.

Press + to interact
for item in seq:
print(item)

The direct iteration approach is faster (by about 30%), doesn’t require creating unnecessary indexes, and is more readable (for [each] item in [that] sequence, do something with [that] item). What if we need to know the index, say, to modify each item in a mutable iterable? For that, we have the built-in function, enumerate(). This returns a generator that ...