Six more examples of ways to refactor your Python code, and why they are improvements
Feb 22, 2022
Writing clean, Pythonic code is all about making it as understandable, yet concise, as possible. This is the eighth part of a series on Python refactorings, based on those that can be done automatically by Sourcery. Here are parts 1, 2, 3, 4, 5, 6, and 7.
The focus of this series is on why these changes are good ideas, not just on how to do them.
Don’t Repeat Yourself (DRY) is a core tenet of programming best practices. Often we think about it for large sections of code, but we also want want to avoid repeating conditionals when possible. For instance - if we’re dealing with a couple of variables that are modified when the same condition holds we might have code structured like:
if wardrobe.hats:
self.happiness += 1
else:
self.happiness -= 1
if wardrobe.hats:
self.stylishness += 1
else:
self.stylishness -= 1
Here we’re repeating both the if wardrobe.hats:
as well as the else:
and can
easily group together the variable changes so we only have a single conditional
check.
if wardrobe.hats:
self.happiness += 1
self.stylishness += 1
else:
self.happiness -= 1
self.stylishness -= 1
We’ve now cut down on the duplication and we’ve significantly shortened and
simplified the code we’re dealing with. An important note is that we can only
simplify our code like this if the statements inside the first if
statement do
not impact the condition itself.
A pattern that we often see when initialising a dictionary is to declare an empty dictionary and then add items into it line by line.
hats_i_own = {}
hats_i_own["panama"] = 1
hats_i_own["baseball_cap"] = 2
hats_i_own["bowler"] = 23
This winds up taking up many more lines than necessary. Instead you can merge the whole assignment process together in a single line - declaring the dictionary alongside the items in it
hats_i_own = {"panama": 1, "baseball_cap": 2, "bowler": 23}
Now while reading the code, if we are not interested in the individual elements the initialisation no longer uses up so much screen real estate, but they are still there if we need to read them.
dict.items()
Sticking with dictionaries for the moment - if we are using both keys and values
from a dictionary we need to call dict.items()
. But, if we’re only calling
for the keys then this call is unnecessary. For instance:
for name, age in people.items():
print("Hi, my name is", name)
In this example we’re grabbing the name of each person in people
, but we’re
only using the dictionary key and never the value (their age) so we don’t
actually need the .items()
. Instead we can simplify the code down to:
for name in people:
print("Hi, my name is", name)
This is a bit nicer to read because we no longer have the unnecessary
dict.items
call and we also are able to remove the unused variable age
.
If we were calling the key and the value of each item in the dictionary we would
need to maintain the dict.items()
.
When we’re printing something in Python we don’t need to explicitly call str()
to print non-strings - it’s done automatically. So if we have something like:
print(str(1))
We can simplify it down to
print(1)
Cutting out the str()
call is a nice, easy way to simplify print statements,
that is also a slight performance improvement.
Nested try-except
statements don’t create the same complexity issues as what
we can run into from
traditional nested conditionals.
But, they’re still best to avoid from an overall readability perspective. For
example:
def testConnection(db, credentials):
try:
try:
db.connect(credentials)
except InvalidCredentials:
return "Check your credentials"
except ConnectionError:
return "Error while trying to connect"
finally:
print("Connection attempt finished")
return "Connection Successful"
Has a nested try
at the top that doesn’t need to be included this is because
try
can contain both a series of except
clauses and a finally
. We can
therefore cut it down to:
def testConnection(db, credentials):
try:
db.connect(credentials)
except InvalidCredentials:
return "Check your credentials"
except ConnectionError:
return "Error while trying to connect"
finally:
print("Connection attempt finished")
return "Connection Successful"
The reduction in nesting has made this code a bit clearer and easier to read.
When we’re dealing with a list we’ll often want to check whether or not a
certain value exists in that list. One common way to handle that is using any
.
if any(hat == "bowler" for hat in hats):
shout("I have a bowler hat!")
This works, but Python’s built in in
operator can make it even easier to
handle this check:
if "bowler" in hats:
shout("I have a bowler hat!")
Now we’re able to do the exact same check, but in a much clearer way. At a quick glance we can see that we’re checking whether an item is in a list.
As mentioned, each of these is a refactoring that Sourcery can automatically perform for you. We're planning on expanding this blog series out and linking them in as additional documentation, with the aim of turning Sourcery into a great resource for learning how to improve your Python skills. You can read the next part in the series here.
If you have any thoughts on how to improve Sourcery or its documentation please do email us or hit me up on Twitter