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
len(tags)
"len(tags) visits every element in the list"
No, it doesn't. A list knows how long it is without looking at all the elements, it is order (1) and it is fast. It's a fine idea.
There is a nice treatise on the concept of "something vs. nothing" in Python from back when Python first introuced a Bool type:
http://mail.python.org/pipermail/python-list/2002-April/136887.html
Anyway, I think the problem here is the use of None to indicate an error — that's what exceptions are for. I'd be inclined to write it:
def parse_file(filename): """ Parse a file, returning a list of tags. Raise FileParseError None on error. """
f = open(filename,'r') if not check_format(f): raise ParseError # file is wrong format tags = [] for line in f: tags.append( parse_line(line) ) return tags
now you can write:
try: tags = parse_file(file) for tag in tags: do_something except ParseError: print "whoops"
Thanks for the heads up!
I have to agree fully with your statements. I have noted several other points bothering me as well, and you can read them here
My specific problems were around databases. Now about 50% of my code is checking and dealing with exceptional cases (not just "exceptions" but also dealing with stuff you don't expect, but that can make your app misbehave).
Is this normal? How much error checking do you have in your code, and how does it compare to other scripting languages like Perl?
Thanks again - Nico
Python helps you to avoid
consider the following, a function called foo that we expect to return a list.
if it fails you can return a null/none or an empty list. if client code has something like:
L=foo(); for(int I=0; I < len(L); ++i) { print(L[i]); }
You can expect this to go bang when foo() fails. If you always return a legal list the app wont die when the client is careless.
If you need better error detection you can return another value denoting an error code or even use exception handling. Both of these are painless and tidy in python.
if len(tags) == 0
# 'Error' handling
elif not tags:
# Empty list case
else:
# Process returned list
len(tags)
len(tags)
I vote for tags is None
Post new comment