Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: override __str__ in an exception instance

I'm trying to override the printed output from an Exception subclass in Python after the exception has been raised and I'm having no luck getting my override to actually be called.

def str_override(self):
    """
    Override the output with a fixed string
    """

    return "Override!"

def reraise(exception):
    """
    Re-raise an exception and override its output
    """

    exception.__str__ = types.MethodType(str_override, exception, type(exception))

    # Re-raise and remove ourselves from the stack trace.
    raise exception, None, sys.exc_info()[-1]

def test():
    """
    Should output "Override!" Actually outputs "Bah Humbug"
    """
    try:
        try:
            raise Exception("Bah Humbug")
        except Exception, e:
            reraise(e, "Said Scrooge")
    except Exception, e:
        print e

Any idea why this doesn't actually override the str method? Introspecting the instance variables shows that the method is actually overridden with the method but it's like Python just refuses to call it through print.

What am I missing here?

like image 753
James Avatar asked May 06 '11 23:05

James


People also ask

How to do custom exceptions in Python?

Creating Custom Exceptions In Python, users can define custom exceptions by creating a new class. This exception class has to be derived, either directly or indirectly, from the built-in Exception class. Most of the built-in exceptions are also derived from this class.

Can you throw exceptions in Python?

Sometimes you want Python to throw a custom exception for error handling. You can do this by checking a condition and raising the exception, if the condition is True. The raised exception typically warns the user or the calling application.

What exception should be raised if an attribute Cannot be found?

The AttributeError in Python is raised when an invalid attribute reference is made, or when an attribute assignment fails. While most objects support attributes, those that do not will merely raise a TypeError when an attribute access attempt is made.


2 Answers

The problem is not that __str__() doesn't get overriden (just like you've already said, it does), but rather that str(e) (which invisibly gets called by print) is not always equivalent to e.__str__(). More specifically, if I get it right, str() (and other special methods, such as repr()), won't look for str in the instance dictionary - it would only look for it in the class dictionary. At least that is the case for the so-called new-style classes (which are the only classes in Python 3.x IIRC). You can read more about it here:

http://mail.python.org/pipermail/python-bugs-list/2005-December/031438.html

If you want to change the exception error message for a reraised exception, you can do something like this instead:

def reraise(exception):
    """
    Re-raise an exception and override its output
    """

    exType = type(exception)
    newExType = type(exType.__name__ + "_Override", (exType,), { '__str__': str_override})
    exception.__class__ = newExType

    # Re-raise and remove ourselves from the stack trace.
    raise exception, None, sys.exc_info()[-1]

This will dynamically derive a new exception class with the str override, and change exception to be an instance of that class. Now your code should work.

like image 79
Boaz Yaniv Avatar answered Oct 26 '22 07:10

Boaz Yaniv


http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes states that "For new-style classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary". IOW, you can't just assign a method to some_instance.__str__. Also, Monkey Patching will not work on builtin types like exceptions. Which you wouldn't want anyway, not even for a non-builtin exception class, since that patch would change the behaviour of all instances of that class.

If you're feelin' kinda hackish you could instead do something like:

...
except DaUncoolException, e:
    e.args = ('cool override stuff!',) + e.args[1:]
    raise

I don't like this very much, though. Why would you want to do such a thing anyway?

like image 42
pillmuncher Avatar answered Oct 26 '22 09:10

pillmuncher