[Python-Dev] PEP 348: Exception Reorganization for Python 3.0

Nick Coghlan ncoghlan at gmail.com
Sat Aug 6 11:33:45 CEST 2005


Guido van Rossum wrote:
> The point is not to avoid bare 'except:' from hiding programming
> errors. There's no hope to obtain that goal.
> 
> The point is to make *legitimate* uses of bare 'except:' easier -- the
> typical use case is an application that has some kind of main loop
> which uses bare 'except:' to catch gross programming errors in other
> parts of the app, or in code received from an imperfect source (like
> an end-user script) and recovers by logging the error and continuing.
> (I was going to say "or clean up and exit", but that use case is
> handled by 'finally:'.)
> 
> Those legitimate uses often need to make a special case of
> Keyboardinterrupt and SystemExit -- KeyboardInterrupt because it's not
> a bug in the code but a request from the user who is *running* the app
> (and the appropriate default response is to exit with a stack trace);
> SystemExit because it's not a bug but a deliberate attempt to exit the
> program -- logging an error would be a mistake.
> 
> I think the use cases for moving other exceptions out of the way are
> weak; MemoryError and SystemError are exceedingly rare and I've never
> felt the need to exclude them; when GeneratorExit or StopIteration
> reach the outer level of an app, it's a bug like all the others that
> bare 'except:' WANTS to catch.

To try to turn this idea into a concrete example, the idea would be to make 
the following code work correctly:

   for job in joblist:
     try:
        job.exec()
     except: # or "except Exception:"
        failed_jobs.append((job, sys.exc_info()))

Currently, this code will make a user swear, as Ctrl-C will cause the program 
to move onto the next job, instead of exiting as you would except (I have 
found Python scripts not exiting when I press Ctrl-C to be an all-too-common 
problem).

Additionally calling sys.exit() inside a job will fail. This may be deliberate 
(to prevent a job from exiting the whole application), but given only the code 
above, it looks like a bug.

The program will attempt to continue in the face of a MemoryError. This is 
actually reasonable, as memory may have been freed as the stack unwound to the 
level of the job execution loop, or the request that failed may have been for 
a ridicuolously large amount of memory.

The program will also attempt to continue in the face of a SystemError. This 
is reasonable too, as SystemError is only used when the VM thinks the current 
operation needs to be aborted due to an internal problem in the VM, but the VM 
itself is still safe to use. If the VM thinks something is seriously wrong 
with the internal data structures, it will kill the process with Py_FatalError 
(to ensure that no further Python code is executed), rather than raise 
SystemError.

As others have pointed out, GeneratorExit and StopIteration should never reach 
the job execution loop - if they do, there's a bug in the job, and they should 
be caught and logged.

That covers the six exceptions that have been proposed to be moved out from 
under "Exception", and, as I see it, only two of them end up making the grade 
- SystemExit and KeyboardInterrupt, for exactly the reasons Guido gives in his 
message above.

This suggests a Py3k exception hierarchy that looks like:

   BaseException
   +-- CriticalException
       +-- SystemExit
       +-- KeyboardInterrupt
   +-- Exception
       +-- GeneratorExit
       +-- (Remainder as for Python 2.4, other than KeyboardInterrupt)

With a transitional 2.x hierarchy that looks like:

   BaseException
   +-- CriticalException
       +-- SystemExit
       +-- KeyboardInterrupt
   +-- Exception
       +-- GeneratorExit
       +-- (Remainder exactly as for Python 2.4)

The reason for the CriticalException parent is that Python 2.x code can be 
made 'correct' by doing:

   try:
       # whatever
   except CriticalException:
       raise
   except: # or 'except Exception'
       # Handle everything non-critical

And, the hypothetical job execution loop above can be updated to:

   for job in joblist:
     try:
        job.exec()
     except CriticalException:
        failed_jobs.append((job, sys.exc_info()))
        job_idx = joblist.find(job)
        skipped_jobs.extend(joblist[job_idx+1:]
        raise
     except: # or "except Exception:"
        failed_jobs.append((job, sys.exc_info()))


To tell the truth, if base except is kept around for Py3k, I would prefer to 
see it catch BaseException rather than Exception. Failing that, I would prefer 
to see it removed. Having it catch something other than the root of the 
exception hierarchy would be just plain confusing.

Moving SystemExit and KeyboardInterrupt is the only change we've considered 
which seems to have a genuine motivating use case. The rest of the changes 
suggested don't seem to be solving an actual problem (or are solving a problem 
that is minor enough to be not worth any backward compatibility pain).

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.blogspot.com


More information about the Python-Dev mailing list