"Code is read more often than it is written"
Guido Van Rossum (Creator of Python)
Keep this thought in the back of your mind, as you go through this article.
Consider the following example:
We have a list of players, with their name and country, and we want to extract all Indian players.
players = [{'name': 'P V Sindhu', 'country': 'India'},
{'name': 'Michael Phelps', 'country': 'USA'},
{'name': 'Usain Bolt', 'country': 'Jamaica'},
{'name': 'Manika Batra', 'country': 'India'}]
indian_players = []
for player in players:
if player['country'] == 'India':
indian_players.append(player)
Now, in the above code, each line is easily understood, but you have to cache four lines in the brain to understand the whole context.
indian_players = [player for player in players if player['country'] == 'India']
Using list comprehension
we can write more expressible code.
The above code also doesn't violate the ITM [Initialize Then Modify] antipattern, where the variable is initialized first and then modified immediately after, as we did in the first example.
LOOP, IF LOOP, PROPER LOOP
Python provides some good abstractions over the looping construct, making code more expressible.
Accessing the elements in a list:
Bad ❌
names = ['P V Sindhu', 'Usain Bolt', 'Michael Phelps', 'Manika Batra']
for i in range(len(names)):
print(names[i])
Good ✔️
for name in names:
print(name)
But what if indices are required?🤔
for i in range(len(names)):
print(i+1, names[i])
No! ❌
Better way ✔️
for idx, name in enumerate(names, start=1):
print(idx, name)
Now, why is the second code better?
Firstly, using indices creates a level of indirection. If the value is required why bother about the index?
Secondly, what if names
was not a list
but a dictionary
, or some other data structure where indexing is not possible?
In that case, the first code would fail as we are indexing in the collection, but the second one would still work correctly.
General rule of thumb, if you are using indices for iteration, Python may have already provided some better abstractions for it.
Some more examples
Iterating over keys and values in a dictionary:
turing_award_winners = {1966: 'Alan Perlis',
1967: 'Maurice Wilkes',
1968: 'Richard Hamming',
1969: 'Marvin Minsky'}
for year in turing_award_winners:
print(year, turing_award_winners[year])
Getting year, and then looking for name in dictionary again causes a level of indirection.
Instead:
for year, name in turing_award_winners.items():
print(year, name)
It also provides clear meaning.
NOT LOOP
Many a time, looping should not be considered at all.
Consider a simple example of finding the sum of all numbers in a list.
Conventional:
nums = [1, 5, 11, 17, 23, 29]
ans = 0
for num in nums:
ans += num
Instead:
ans = sum(nums)
The code conveys the idea, that ans
contains the sum of all numbers in the list nums
, and is also very concise.
Conclusion
Write more expressible code, by utilizing the different constructs that the language has to offer. Avoid raw loops whenever you can.
I highly recommend to watch the videos below.
Further Watch:
- Loop like a native: while, for, iterators, generators by Ned Batchelder.
- Beautiful Python Refactoring by Conor Hoekstra.
- C++ Seasoning by Sean Parent.
Top comments (1)
Better still:
Horizontal scrolling is the devil, and vertical space is cheap. Plus, breaking long statements up into logical sub-statements on multiple lines makes code more readable. See also: Semantic Newlines for prose text.
(Offtopic: Dev's comment editor doesn't support semantic breaks, because it preserves all newlines in the MarkDown source text. Which is disappointing.)