On 05/07/13 05:40, Albert-Jan Roskam wrote:
Hello,

I created a custom error class like under [A] below (see 
http://pastebin.com/JLwyPsRL if you want to see highlighted code). Now I am 
thinking: is this specific enough? "When
creating a module that can raise several distinct errors, a common practice is
to create a base class for exceptions defined by that module, and subclass that
to create specific exception classes for different error conditions" 
(http://docs.python.org/2/tutorial/errors.html)
Given that I have many retcodes, is it a better approach to define a class 
factory that generates a custom exception for each retcode on-the-fly? (under 
[B]). Using a class factory seems a little unusual, or isn it? More generally: 
what criteria should one follow when distinguishing error classes?

Return codes are not exceptions, and generally speaking using return codes for errors is 
*strongly* discouraged unless you have a language without exceptions. (Although the 
designers of Go language disagree. I think they are wrong to do so.) Although looking 
forward, I see that even though you call them "retcodes", you don't actually 
use them as return codes.


global retcodes
retcodes = {0: "OK", 1: "Error_X", 2: "Error_Y"}

You don't need to declare a global variable global in the global part of your file. 
Although Python allows it, it is redundant and rather silly. Delete the line "global 
retcodes".


#############################
# [A] one custom exception for all purposes, but with a descriptive message
#############################

class single_custom_exception(Exception):
     def __init__(self, retcode, message):
         self.retcode = retcode
         message += " [%s]" % retcodes.get(retcode)
         Exception.__init__(self, message)

Exceptions are classes, and like all classes, the convention is to name them in 
CamelCase.


#############################
# [B] generating custom exceptions with a class factory
#############################

class BaseError(Exception): pass

def throw(retcode, message):
     """class factory that generates an error class for
     each return code label"""
     class custom_exception(BaseError):
         def __init__(self):
             BaseError.__init__(self, message)
     exception = retcodes.get(retcode)
     custom_exception.__name__ = exception
     return custom_exception

This is overkill. No need to create a custom class *on the fly* for each return 
code. Doing so is pointless: the caller *cannot* catch them individually, 
because they are created on the fly. So they can only catch BaseError, then 
inspect the exception's details. If you have to do that, then having individual 
classes is a waste of time. You might as well just use a single class and be 
done with it.

What you would like to do, but cannot, since neither Error_X nor Error_Y are 
defined:

try:
    code_that_might_fail()
except Error_X:
    handle_error_X
except Error_Y:
    handle_error_Y



What you are forced to do:

try:
    code_that_might_fail()
except BaseError as err:
    if err.retcode == 0:
        print ("Error: no error occurred!")
    elif err.retcode == 1:
        handle_error_X
    elif err.retcode == 1:
        handle_error_Y
    else:
        print ("Error: an unexpected error occurred!")


which is ugly enough, but it makes the class factory redundant.

So, how should you handle this scenario?

Use a separate exception class for each high-level *category* of errors. E.g. 
Python has:

TypeError
ValueError
ZeroDivisionError
UnicodeEncodeError

etc. Within your app, can you divide the return codes into application-specific 
groups? If not, then you just need a single exception class:

MyApplicationError


If you do have a bunch of application-specific groups, then you might develop a 
hierarchical family of exceptions:


WidgetError
+-- WrongWidgetTypeError
+-- InvalidWidgetError
+-- WidgetNotInitialisedError


WidgetGroupError
+-- MissingWidgetError
|   +-- MissingCriticalWidgetError
+-- TooManyWidgetsError


SpoonError
+-- TeaspoonError
+-- SoupSpoonError
+-- DesertSpoonError


The parent exceptions, WidgetError, WidgetGroupError and SpoonError do not need 
to share a common base class.

This hierarchy might completely obliterate the need for return codes. Python's 
build-in exceptions generally do not. However, maybe you do still need them, 
similar to the way Python's OSError and IOError have error codes. Assuming you 
do:


def someFunc():
    perform_calculations()
    if there are no spoons:
        raise TeaspoonError("cannot stir my tea")
    elif some other error:
        raise InvalidWidgetError(retcode=99, message="Can't use red widget")
    else:
        return some value



--
Steven
_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor

Reply via email to