Here are a couple of cases where not explicitly testing for None has gotten me into trouble. Maybe these can help someone else avoid the same headaches.
Look at this sample:
Simple parsing function
def parse_file(filename):
"""
Parse a file, returning a list of tags.
Returns None on error.
"""
f = open(filename,'r')
if not check_format(f):
return None # file is wrong format
tags = []
for line in f:
tags.append( parse_line(line) )
return tags
if parse_file(filename):
print "Parsed OK!"
else:
print "** ERROR **"
"""
Parse a file, returning a list of tags.
Returns None on error.
"""
f = open(filename,'r')
if not check_format(f):
return None # file is wrong format
tags = []
for line in f:
tags.append( parse_line(line) )
return tags
if parse_file(filename):
print "Parsed OK!"
else:
print "** ERROR **"
The boolean value of 'empty'
if []: print "True"
if not []: print "False"
if not []: print "False"
I think the thing to do is recognize that this function has three exit states:
- None, indicating an error.
- An empty list, indicating an empty file.
- A non-empty list, holding tags.
Explicitly test for None
tags = parse_file(filename)
if tags is None:
print "** ERROR **"
elif len(tags) == 0:
print "Empty file"
else:
print "OK!"
if tags is None:
print "** ERROR **"
elif len(tags) == 0:
print "Empty file"
else:
print "OK!"
Now with four exit states ...
def parse_file(filename):
"""
Parse a file, returning a list of tags.
Returns None on error.
"""
f = open(filename,'r')
if not check_format(f):
return None # file is wrong format
tags = []
for line in f:
# look for special end-of-file tag
if end_of_file(l):
return tags
else:
tags.append( parse_line(line) )
"""
Parse a file, returning a list of tags.
Returns None on error.
"""
f = open(filename,'r')
if not check_format(f):
return None # file is wrong format
tags = []
for line in f:
# look for special end-of-file tag
if end_of_file(l):
return tags
else:
tags.append( parse_line(line) )
Not returning a value == None
def foo():
pass
print "The value is %s" % foo()
Prints "The value is None".
pass
print "The value is %s" % foo()
Prints "The value is None".
Anyways, disregarding the buggy code for the moment, recognize that the above function has four distinct exit states:
- None, indicating an error.
- An empty list, indicating an empty file.
- A non-empty list, holding tags.
- None, indicating no return value.
- The value None.
- The absence of a value.
I appreciate that Python is a practical language. An impractical language could "fix" this by forcing you to only use (exactly) True or False in boolean expressions. Python tends to loosen the rules as much as practical, without going overboard. (Some languages like perl go overboard in their coercion rules, which I think leads to even harder to understand code.). I wish that empty lists didn't evaluate to False, but that's the way it is, so you just have to keep it in mind.
NOTE
Normally, if you don't like the way an object behaves, you can subclass it and override the behavior you don't like. In the case of boolean operators, there doesn't seem to be a way to do that. If L is a list, the expression if L: ... calls L.__len__(). Therefore an empty list returns 0, which is False. Trying to override this would break other list functionality. There is a draft proposal, PEP 335: Overloadable Boolean Operators, but even this doesn't seem to allow you to override the case of if L: ..., only the case if not L: ....
Written in WikklyText.

Comments
if len(tags) == 0
# 'Error' handling
elif not tags:
# Empty list case
else:
# Process returned list
len(tags)
Post new comment