Exceptions
def complement(c):
if c == 'A':
return 'T'
if c == 'T':
return 'A'
if c == 'C':
return 'G'
if c == 'G':
return 'C'
is equivalent to:
def complement(c):
if c == 'A':
return 'T'
if c == 'T':
return 'A'
if c == 'C':
return 'G'
if c == 'G':
return 'C'
return None
complement_seq
is a client of complement
:
def complement_seq(dna_seq):
return ''.join(complement(b) for b in dna_seq[::-1])
Unfolded, to make debugging easier:
def complement_seq(dna_seq):
result = ''
for b in dna_seq[::-1]:
c = complement(b)
result += c
return result
Passing an invalid argument to complement_seq
passes an invalid argument to complement
, which raises an exception. The exception is downstream from the call to complement
, and has an unrevealing name and message. This makes this difficult to debug.
complement_seq('CAXT')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-46-cef160c3921b> in <module>()
----> 1 complement_seq('CAXT')
<ipython-input-45-1a6d8e3928b2> in complement_seq(dna_seq)
3 for b in dna_seq[::-1]:
4 c = complement(b)
----> 5 result += c
6 return result
TypeError: Can't convert 'NoneType' object to str implicitly
Return-value-as-error
One technique (frowned on in Python) is to represent an error by an “out-of-band” value. “Out-of-band” means not in the set of valid return values for the function.
def complement(c):
if c == 'A':
return 'T'
if c == 'T':
return 'A'
if c == 'C':
return 'G'
if c == 'G':
return 'C'
return 'error'
complement
callers need to know about this. If they don’t know how to recover from the error, they should return an out-of-band value too. Then their callers need to follow this convention as well.
def complement_seq(dna_seq):
result = ''
for b in dna_seq[::-1]:
c = complement(b)
if c == 'error':
return 'error'
result += c
return result
def function_that_uses_complement_seq():
# do some stuff that computes dna_seq
# ...
comp_seq = complement_seq(dna_seq)
if comp_seq == 'error':
return 'error'
# now the case where comp_seq didn't return an error
Exceptions
The alternative to returning a value is to raise an *exception:
def complement(c):
if c == 'A':
return 'T'
if c == 'T':
return 'A'
if c == 'C':
return 'G'
if c == 'G':
return 'C'
raise Exception('invalid nucleobase')
complement('X')
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-47-6c045add2467> in <module>()
10 raise Exception('invalid nucleobase')
11
---> 12 complement('X')
<ipython-input-47-6c045add2467> in complement(c)
8 if c == 'G':
9 return 'C'
---> 10 raise Exception('invalid nucleobase')
11
12 complement('X')
Exception: invalid nucleobase
The exception is be thrown straight through complement
’s callers – even if they don’t know about exceptions. This makes for easier debugging.
def complement_seq(dna_seq):
result = ''
for b in dna_seq[::-1]:
c = complement(b)
result += c
return result
complement_seq('CAXT')
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-49-2d23b82ff07b> in <module>()
6 return result
7
----> 8 complement_seq('CAXT')
<ipython-input-49-2d23b82ff07b> in complement_seq(dna_seq)
2 result = ''
3 for b in dna_seq[::-1]:
----> 4 c = complement(b)
5 result += c
6 return result
<ipython-input-47-6c045add2467> in complement(c)
8 if c == 'G':
9 return 'C'
---> 10 raise Exception('invalid nucleobase')
11
12 complement('X')
Exception: invalid nucleobase
Catching (or handling) exceptions
pay_me_a_complement
is a client of complement_seq
.
The straightforward implementation displays a stack trace when the user enters an invalid sequence.
def pay_me_a_complement():
seq = input()
print('The complement is', complement_seq(seq))
pay_me_a_complement()
CAXT
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-50-0f7ab1192a67> in <module>()
3 print('complement is', complement_seq(seq))
4
----> 5 pay_me_a_complement()
<ipython-input-50-0f7ab1192a67> in pay_me_a_complement()
1 def pay_me_a_complement():
2 seq = input()
----> 3 print('complement is', complement_seq(seq))
4
5 pay_me_a_complement()
<ipython-input-49-2d23b82ff07b> in complement_seq(dna_seq)
2 result = ''
3 for b in dna_seq[::-1]:
----> 4 c = complement(b)
5 result += c
6 return result
<ipython-input-47-6c045add2467> in complement(c)
8 if c == 'G':
9 return 'C'
---> 10 raise Exception('invalid nucleobase')
11
12 complement('X')
Exception: invalid nucleobase
Use try…except
to handle exceptions.
The following code acts normally the same as the implementation above the code in the try
block runs without exception.
If, however, there’s an exception within the try
block, then the program skips the rest of that block and picks up at the start of the except
block instead.
def pay_me_a_complement():
seq = input()
try:
print('The complement is', complement_seq(seq))
except:
print('Invalid DNA sequence: {}'.format(seq))
print('done')
pay_me_a_complement()
CAXT
Invalid DNA sequence: CAXT
done
User-Defined Exception Class
The previous implementation indiscriminately turns all program errors into an “Invalid DNA sequence” message.
It’s equivalent to the following function. except Exception
means catch any exception that is an instance of the class Exception
– but this is all exceptions.
def pay_me_a_complement():
seq = input()
try:
print('The complement is', complement_seq(seq))
except Exception:
print('Invalid sequence')
print('done')
pay_me_a_complement()
We can write a more specific except
clause, to handle a more specific exception:
class InvalidNucleobaseException(Exception):
pass
def complement(c):
if c == 'A':
return 'T'
if c == 'T':
return 'A'
if c == 'C':
return 'G'
if c == 'G':
return 'C'
raise InvalidNucleobaseException('invalid nucleobase')
def pay_me_a_complement():
seq = input()
try:
print('The complement is', complement_seq(seq))
except InvalidNucleobaseException:
print('Invalid DNA sequence: {}'.format(seq))
pay_me_a_complement()
CAXT
Invalid DNA sequence: CAXT