slightly OT: Python BootCamp
Steven D'Aprano
steve at REMOVE-THIS-cybersource.com.au
Thu Dec 3 20:21:28 EST 2009
On Thu, 03 Dec 2009 16:20:24 -0500, J wrote:
> The difference between me (learning a new language) and the guy teaching
> (who knows it inside and out)
I don't know about that. He's teaching some pretty atrocious habits. See
below.
> His code:
[...]
> for (exten, list) in files.iteritems():
> try:
> f=open('extensions-%s.txt' % exten,'w')
> f.write('\n'.join(list))
> except:
> assert 0, 'Unable to create output file extensions-%s.txt.' %
> exten
Wrong! Bad! Don't do this!!!
There are no less than five errors here. Some people might argue that
given the context (a short script), none of them matter in practice. For
a script you use once then throw away, maybe. I've written sloppy scripts
myself, and lazy can be a virtue. But *teaching* sloppy is a terrible
thing -- even if you disagree that this are the sorts of errors that
matter in this script, they are bad, bad habits that you should avoid.
Error #1: Bare excepts are nearly always wrong. Bare excepts catch
*everything*, including exceptions caused by bugs in your code. Including
the user trying to interrupt the code with ctrl-D. Don't use bare excepts
unless you know what you're doing. Catch the specific exceptions you
actually want instead.
Error #2: Wrapping multiple operations in a single try block. This
conflates errors in four different operations:
generate the file name
open the file
merge strings into a single newline-delimited string
write the strings to the file
The instructor assumes that only the open can fail. That's incorrect. His
try...except will mask coding errors, as well as conflating open errors
and write errors.
Error #3: Throwing away useful debugging information and replacing it
with a generic error message that isn't useful.
If an error occurs, Python will generate a useful exception telling
exactly what error occurred and why. If it happens in a file operation,
it will include the error code. The given code throws that away and
replaces it with a generic error that tells you nothing except that an
assertion failed.
Error #4: Using assert for anything other than what it was designed for.
If you want to break this script, run it with optimizations switched on
and trigger a failure-mode (perhaps by removing write-permission from the
current directory). With optimizations switched on, the assert statement
will be compiled away, and the script will catch the open errors and
ignore them.
Error #5: Failing to close each file after you're done with it. Python
does close files for you, if you don't do it yourself, but when it does
so is implementation dependent. Some versions of Python won't close them
until the very end of the program, as it closes down. That means that if
the list of files is large enough, you will run out of file descriptors
and the program will stop working.
How would I re-write this? Just get rid of the try block and add a close:
for (exten, list) in files.iteritems():
f=open('extensions-%s.txt' % exten,'w')
f.write('\n'.join(list))
f.close()
If an error occurs, Python's normal exception-raising mechanism will halt
the script and print a useful error message.
If you want to mask the traceback from the user (but I'm not sure why you
would want to...) then wrap the entire loop in a single try, catching
keyboard interrupt separately:
try:
for (exten, list) in files.iteritems():
f=open('extensions-%s.txt' % exten,'w')
f.write('\n'.join(list))
f.close()
except KeyboardInterrupt:
print "Interrupted by user."
raise
except Exception:
raise ValueError('failed for some unspecified reason')
That's still not quite "best practice" -- best practice would be to use a
with block, but (to my shame) I'm not entirely familiar with that so I'll
leave it for others.
--
Steven
More information about the Python-list
mailing list