The Most Diabolical Python Antipattern

Michiel Overtoom motoom at xs4all.nl
Fri Jan 30 02:09:02 EST 2015


On Jan 29, 2015, at 18:36, Skip Montanaro wrote:

> There are the occasional instance where I need to recover
> from an exception no matter what caused it.

I had the same need, a while ago, when working on a CherryPy webapp which uses a BackgroundTask to parse user-uploaded data and image files (and that can throw a variety of exceptions). Any uncaught exception from a BackgroundTask will terminate the background thread, which is not good, because one rotten input file shouldn't spoil it for the rest. It was also unpredictable what kind of exceptions underlying modules would throw without going through all the sources with a fine comb. Thus, I wrote a decorator which will catch any exception (but not KeyboardInterrupt and SystemExit of course):


# alwayscatch.py - Software by Michiel Overtoom, motoom at xs4all.nl
#
# decorator to wrap an entire function in a try..except block
# to prevent any exception to reach the caller (except SystemExit and KeyboardInterrupt)
# because in that case some vital backgroundtask in a webframework will stop working.

def alwayscatch(logfunc=None, locus=None):
    def deco(wrappee):
        def wrapper(*args, **kwargs):
            try:
                return wrappee(*args, **kwargs)
            except KeyboardInterrupt, SystemExit:
                raise
            except Exception as e:
                if logfunc:
                    logfunc(locus, "Developer sloppyness and/or unrecognized situation", e)
                else:
                    print "No logging func defined"
        return wrapper
    return deco
    

def frameworklogfunc(locus, msg, exc):
    print "[%s] %s: '%s'" % (locus, msg, str(exc))

    
@alwayscatch(frameworklogfunc, "SUBSYSTEM")
def protectedfunc(x, y, color=23):
    return x / y / color


@alwayscatch()
def sillyfunc(a, b, c):
    return a / b / c
    

if __name__ == "__main__":
    print protectedfunc(900, 2, 0)
    print sillyfunc(900, 2, 0)


...and I used it like this, from the main program:

sizer = cherrypy.process.plugins.BackgroundTask(10, sizer.scan)
sizer.start()

...where the worker function is defined like this:

@alwayscatch(scanlogerror, "SIZE")
def scan():
    ...

...and the custom logging function: (cherrypy.log in its turn uses the standard logging framework)

def scanlogerror(locus, msg, exc):
    cherrypy.log("Uncaught exception: %s" % exc, locus, logging.ERROR)


Greetings,

-- 
"You can't actually make computers run faster, you can only make them do less." - RiderOfGiraffes




More information about the Python-list mailing list