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:
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:
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?
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:
tc = (c for c in "Mary had a little lamb" if c in string.ascii_letters)print(''.join(tc)) # Printing without spaceprint(' '.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:
#seq=[5,6,7,8,9]for i in range(len(seq)):do_something_with(seq[i])
Let’s print the seq
using range()
.
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):
for item in seq:do_something_with(item)
Let’s print the items.
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 ...