Commit 44ef7749 authored by R. David Murray's avatar R. David Murray

#9608, #8518 : clarify and improve discussion of exceptions in howto.

parent 41ece39c
...@@ -111,30 +111,40 @@ except: ...@@ -111,30 +111,40 @@ except:
------- -------
Python has the ``except:`` clause, which catches all exceptions. Since *every* Python has the ``except:`` clause, which catches all exceptions. Since *every*
error in Python raises an exception, this makes many programming errors look error in Python raises an exception, using ``except:`` can make many
like runtime problems, and hinders the debugging process. programming errors look like runtime problems, which hinders the debugging
process.
The following code shows a great example:: The following code shows a great example of why this is bad::
try: try:
foo = opne("file") # misspelled "open" foo = opne("file") # misspelled "open"
except: except:
sys.exit("could not open file!") sys.exit("could not open file!")
The second line triggers a :exc:`NameError` which is caught by the except The second line triggers a :exc:`NameError`, which is caught by the except
clause. The program will exit, and you will have no idea that this has nothing clause. The program will exit, and the error message the program prints will
to do with the readability of ``"file"``. make you think the problem is the readability of ``"file"`` when in fact
the real error has nothing to do with ``"file"``.
The example above is better written :: A better way to write the above is ::
try: try:
foo = opne("file") # will be changed to "open" as soon as we run it foo = opne("file")
except IOError: except IOError:
sys.exit("could not open file") sys.exit("could not open file")
There are some situations in which the ``except:`` clause is useful: for When this is run, Python will produce a traceback showing the :exc:`NameError`,
example, in a framework when running callbacks, it is good not to let any and it will be immediately apparent what needs to be fixed.
callback disturb the framework.
.. index:: bare except, except; bare
Because ``except:`` catches *all* exceptions, including :exc:`SystemExit`,
:exc:`KeyboardInterrupt`, and :exc:`GeneratorExit` (which is not an error and
should not normally be caught by user code), using a bare ``except:`` is almost
never a good idea. In situations where you need to catch all "normal" errors,
such as in a framework that runs callbacks, you can catch the base class for
all normal exceptions, :exc:`Exception`.
Exceptions Exceptions
...@@ -152,40 +162,43 @@ The following is a very popular anti-idiom :: ...@@ -152,40 +162,43 @@ The following is a very popular anti-idiom ::
sys.exit(1) sys.exit(1)
return open(file).readline() return open(file).readline()
Consider the case the file gets deleted between the time the call to Consider the case where the file gets deleted between the time the call to
:func:`os.path.exists` is made and the time :func:`open` is called. That means :func:`os.path.exists` is made and the time :func:`open` is called. In that
the last line will raise an :exc:`IOError`. The same would happen if *file* case the last line will raise an :exc:`IOError`. The same thing would happen
exists but has no read permission. Since testing this on a normal machine on if *file* exists but has no read permission. Since testing this on a normal
existing and non-existing files make it seem bugless, that means in testing the machine on existent and non-existent files makes it seem bugless, the test
results will seem fine, and the code will get shipped. Then an unhandled results will seem fine, and the code will get shipped. Later an unhandled
:exc:`IOError` escapes to the user, who has to watch the ugly traceback. :exc:`IOError` (or perhaps some other :exc:`EnvironmentError`) escapes to the
user, who gets to watch the ugly traceback.
Here is a better way to do it. :: Here is a somewhat better way to do it. ::
def get_status(file): def get_status(file):
try: try:
return open(file).readline() return open(file).readline()
except (IOError, OSError): except EnvironmentError as err:
print("file not found") print("Unable to open file: {}".format(err))
sys.exit(1) sys.exit(1)
In this version, \*either\* the file gets opened and the line is read (so it In this version, *either* the file gets opened and the line is read (so it
works even on flaky NFS or SMB connections), or the message is printed and the works even on flaky NFS or SMB connections), or an error message is printed
application aborted. that provides all the available information on why the open failed, and the
application is aborted.
Still, :func:`get_status` makes too many assumptions --- that it will only be However, even this version of :func:`get_status` makes too many assumptions ---
used in a short running script, and not, say, in a long running server. Sure, that it will only be used in a short running script, and not, say, in a long
the caller could do something like :: running server. Sure, the caller could do something like ::
try: try:
status = get_status(log) status = get_status(log)
except SystemExit: except SystemExit:
status = None status = None
So, try to make as few ``except`` clauses in your code --- those will usually be But there is a better way. You should try to use as few ``except`` clauses in
a catch-all in the :func:`main`, or inside calls which should always succeed. your code as you can --- the ones you do use will usually be inside calls which
should always succeed, or a catch-all in a main function.
So, the best version is probably :: So, an even better version of :func:`get_status()` is probably ::
def get_status(file): def get_status(file):
return open(file).readline() return open(file).readline()
...@@ -194,9 +207,15 @@ The caller can deal with the exception if it wants (for example, if it tries ...@@ -194,9 +207,15 @@ The caller can deal with the exception if it wants (for example, if it tries
several files in a loop), or just let the exception filter upwards to *its* several files in a loop), or just let the exception filter upwards to *its*
caller. caller.
The last version is not very good either --- due to implementation details, the But the last version still has a serious problem --- due to implementation
file would not be closed when an exception is raised until the handler finishes, details in CPython, the file would not be closed when an exception is raised
and perhaps not at all in non-C implementations (e.g., Jython). :: until the exception handler finishes; and, worse, in other implementations
(e.g., Jython) it might not be closed at all regardless of whether or not
an exception is raised.
The best version of this function uses the ``open()`` call as a context
manager, which will ensure that the file gets closed as soon as the
function returns::
def get_status(file): def get_status(file):
with open(file) as fp: with open(file) as fp:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment