The Alternate Egos of Python's 'Else'
Python's else
keyword has some neat functionalities that I haven't run into in
other programming languages. As someone new to the language, you'll obviously know the
if
/else
block, exemplified in this simplified selection of a
preposition (
if you go to this article, you can find plenty of examples where this function is
inadequate):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def select_preposition(word):
''' Selects a preposition for the given word
:param word: String which preposition will be inserted before
:return: Selected preposition (a or an)
'''
if word.lower()[0] in 'aeiou':
return 'an'
else:
return 'a'
# Main function
if __name__ == '__main__':
words = [
'Man',
'Animal',
'House',
'Wombat'
]
for word in words:
preposition = select_preposition(word)
print(f'{preposition} {word.lower()}')
Of course, that gives you:
a man
an animal
a house
a wombat
But did you also know that the else keyword can be used with for loops?
Here's an example of a script which adds friends' contact information to a spreadsheet if an entry has not been found:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def get_contact_info(name):
''' Gets the contact information or adds it to the spreadsheet
if not available
:param name: Name of contact to add
:return: Contact information
'''
# Initialize empty record for new contacts
record = {
'name': name,
'phone': '',
'email': ''
}
# Find contact in contact_info.csv. If one is found,
# break from the loop early to avoid the else condition
with open('contact_info.csv', 'a+') as contact_info:
contact_info.seek(0)
for line in contact_info:
if name in line:
record = {list(record.keys())[count]: val
for count, val in enumerate(line.split(','))}
break
# No contact found. Add new entry to spreadsheet
else:
contact_info.write(
f'{record['name']},{record['phone']},{record['email']}\n')
return record
# Main function
if __name__ == '__main__':
for name in ['Janet', 'Chris', 'Bob']:
contact_info = get_contact_info(name)
print(f'Name:{contact_info["name"]}\n'
f'Phone:{contact_info["phone"]}\n'
f'Email:{contact_info["email"]}')
Within the with
block, each line of the file is searched for the friend's name.
If the name is found, a new record with the contact information is populated and the loop is
exited with the break
keyword. This avoids the functionality in the else
block of the loop.
If the loop is able to complete without breaking, the else
block is executed. In
this case, that means that a record was not found for the friend that was requested. A new record
is written to the CSV.
Here's the output of the program when Janet's and Chris' contact information is in the CSV and Bob must be added:
Name:Janet
Phone:555-1234
Email:[email protected]
Name:Chris
Phone:555-5555
Email:[email protected]
Name:Bob
Phone:
Email:
The contents of the file after execution looks like this:
Janet,555-1234,[email protected]
Chris,555-5555,[email protected]
Bob,,
No new records were created for Janet and Chris, since they were found in the file and the function broke early from the loop. A new record was created for Bob since the loop was allowed to complete.
The same functionality can be used for the while
statement.
The else
keyword's third hat is related to the try
/
except
block. It can be placed after except to execute any code which should only
run if an exception is not encountered. Note that this is different than a finally
block which runs after the try
/except
whether or not there was an
exception encountered.
The next example of a file reader demonstrates the difference between the 'finally' and 'else'
keywords in the context of a try
/except
block.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def read_file_contents(path):
''' Prints the file contents and number of characters read
:param path: Path to file
:return: None
'''
# Attempt to open the file. If the file is not found, a
# FileNotFoundError will be raised.
try:
file_length = 0
contents = ''
print(('-' * 10) + path + ('-' * 10))
file_handle = open(path, 'r')
# Handle the FileNotFoundError by setting the file 'contents'
# to the string below. Execution will then skip the 'else'
# block since an exception was encountered.
except FileNotFoundError:
contents = f'File {path} not found'
# The open completed without an exception. We can now read
# from the file handle and calculate the length of the file.
else:
contents = file_handle.read()
file_length = len(contents)
# Regardless of whether an exception was raised, print the
# number of bytes read and the file contents (or our fake
# contents if nothing was read).
finally:
print(f'Characters read: {file_length}\n')
print(contents)
# Main function reads from a good file (contact_info.csv from last
# example), or a file which cannot be found.
if __name__ == '__main__':
read_file_contents('contact_info.csv')
read_file_contents('not_a_file.txt')
This script results in the following:
----------contact_info.csv----------
Characters read: 82
Janet,555-1234,[email protected]
Chris,555-5555,[email protected]
Bob,,
----------not_a_file.txt----------
Characters read: 0
File not_a_file.txt not found
You can see that the except
and the else
block are exclusive,
while the finally
block will always be executed, even if an exception is raised.
Note that if there are multiple except
blocks (for multiple exception types),
there is still only one else
block. No need for an else
after
each exception type.
I find that this is really helpful in writing explicit code. Instead of having to guess where
exceptions come from in a giant try
block, the else
keyword
guides the readers of your code to what is expected to raise an exception, and what is just a
dependency of the suspect code (and is not expected to raise an exception itself).
And isn't explicit code the beauty of Python?