Re-raising exceptions with modified message

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • samwyse

    #16
    Re: Re-raising exceptions with modified message

    On Jul 7, 4:13 pm, samwyse <samw...@gmail. comwrote:
    On Jul 5, 8:53 am, Christoph Zwerschke <c...@online.de wrote:
    >
    What is the best way to re-raise any exception with a message
    supplemented with additional information (e.g. line number in a
    template)?
    [...]
    That leaves the issue of the name being changed for
    UnicodeDecodeEr ror, which might be fixable by diddling with __name__
    properties. Or perhaps SorryEx needs to be a factory that returns
    exception classes; the last line would be "SorryEx(e) ()". I'll have
    to play with this a bit.
    OK, the following mostly works. You probably want the factory to copy
    more of the original class into the SorryEx class each time, since
    someone catching an exception may expect to look at things besides its
    string representation.

    def SorryFactory(e) :
    class SorryEx(Excepti on):
    def __init__(self):
    self._e = e
    def __getattr__(sel f, name):
    return getattr(self._e , name)
    def __str__(self):
    return str(self._e) + ", sorry!"
    SorryEx.__name_ _ = e.__class__.__n ame__
    return SorryEx

    def test(code):
    try:
    code()
    except Exception, e:
    try:
    raise e.__class__, str(e) + ", sorry!"
    except TypeError:
    raise SorryFactory(e) ()

    test(lambda: unicode('\xe4') )


    Comment

    • Christoph Zwerschke

      #17
      Re: Re-raising exceptions with modified message

      Did you run this?
      With Py < 2.5 I get a syntax error, and with Py 2.5 I get:

      new.__class__ = old.__class__
      TypeError: __class__ must be set to a class

      -- Chris

      Comment

      • Christoph Zwerschke

        #18
        Re: Re-raising exceptions with modified message

        samwyse wrote:
        def test(code):
        try:
        code()
        except Exception, e:
        try:
        raise e.__class__, str(e) + ", sorry!"
        except TypeError:
        raise SorryFactory(e) ()
        Ok, you're suggestig the naive approach if it works and the factory
        approach I came up with last as a fallback. Maybe a suitable compromize.

        -- Chris

        Comment

        • samwyse

          #19
          Re: Re-raising exceptions with modified message

          On Jul 12, 6:31 am, samwyse <samw...@gmail. comwrote:
          On Jul 8, 8:50 am, Christoph Zwerschke <c...@online.de wrote:
          >
          With Py 2.5 I get:
          >
          new.__class__ = old.__class__
          TypeError: __class__ must be set to a class

          Hmmm, under Python 2.4.X, printing repr(old.__clas s__) gives me this:
          <class exceptions.Unic odeDecodeError at 0x00A24F00>
          while under 2.5.X, I get this:
          <type 'exceptions.Uni codeDecodeError '>


          So, let's try sub-classing the type:

          def modify_message( old, f):
          class Empty: pass
          new = Empty()
          print "old.__clas s__ =", repr(old.__clas s__)
          print "Empty =", repr(Empty)
          new.__class__ = Empty

          class Excpt(old.__cla ss__): pass
          print "Excpt =", repr(Excpt)
          print "Excpt.__class_ _ =", repr(Excpt.__cl ass__)
          new.__class__ = Excpt

          new.__dict__ = old.__dict__.co py()
          new.__str__ = f
          return new

          Nope, that gives us the same message:

          old.__class__ = <type 'exceptions.Uni codeDecodeError '>
          Empty = <class __main__.Empty at 0x00AB0AB0>
          Excpt = <class '__main__.Excpt '>
          Excpt.__class__ = <type 'type'>
          Traceback (most recent call last):
          [...]
          TypeError: __class__ must be set to a class

          Excpt ceratinly appears to be a class. Does anyone smarter than me
          know what's going on here?

          Comment

          • Christoph Zwerschke

            #20
            Re: Re-raising exceptions with modified message

            samwyse wrote:
            TypeError: __class__ must be set to a class
            >
            Excpt ceratinly appears to be a class. Does anyone smarter than me
            know what's going on here?
            Not that I want to appear smarter, but I think the problem here is that
            exceptions are new-style classes now, whereas Empty is an old-style
            class. But even if you define Empty as a new-style class, it will not
            work, you get:

            TypeError: __class__ assignment: only for heap types

            This tells us that we cannot change the attributes of a built-in
            exception. If it would be possible, I simply would have overridden the
            __str__ method of the original exception in the first place.

            -- Chris


            Comment

            • samwyse

              #21
              Assignments to __class_ broken in Python 2.5?

              On Jul 12, 11:48 am, samwyse <samw...@gmail. comwrote:
              On Jul 12, 6:31 am,samwyse<samw ...@gmail.comwr ote:
              >
              On Jul 8, 8:50 am, Christoph Zwerschke <c...@online.de wrote:
              >
              With Py 2.5 I get:
              >
              new.__class__ = old.__class__
              TypeError: __class__ must be set to a class
              >
              Hmmm, under Python 2.4.X, printing repr(old.__clas s__) gives me this:
              <class exceptions.Unic odeDecodeError at 0x00A24F00>
              while under 2.5.X, I get this:
              <type 'exceptions.Uni codeDecodeError '>
              >
              So, let's try sub-classing the type:
              >
              def modify_message( old, f):
              class Empty: pass
              new = Empty()
              print "old.__clas s__ =", repr(old.__clas s__)
              print "Empty =", repr(Empty)
              new.__class__ = Empty
              >
              class Excpt(old.__cla ss__): pass
              print "Excpt =", repr(Excpt)
              print "Excpt.__class_ _ =", repr(Excpt.__cl ass__)
              new.__class__ = Excpt
              >
              new.__dict__ = old.__dict__.co py()
              new.__str__ = f
              return new
              >
              Nope, that gives us the same message:
              >
              old.__class__ = <type 'exceptions.Uni codeDecodeError '>
              Empty = <class __main__.Empty at 0x00AB0AB0>
              Excpt = <class '__main__.Excpt '>
              Excpt.__class__ = <type 'type'>
              Traceback (most recent call last):
              [...]
              TypeError: __class__ must be set to a class
              >
              Excpt certainly appears to be a class. Does anyone smarter than me
              know what's going on here?
              OK, in classobject.h, we find this:

              #define PyClass_Check(o p) ((op)->ob_type == &PyClass_Typ e)

              That seems straightforward enough. And the relevant message appears
              in classobject.c here:

              static int
              instance_setatt r(PyInstanceObj ect *inst, PyObject *name, PyObject *v)
              [...]
              if (strcmp(sname, "__class__" ) == 0) {
              if (v == NULL || !PyClass_Check( v)) {
              PyErr_SetString (PyExc_TypeErro r,
              "__class__ must be set to a class");
              return -1;
              }

              Back in our test code, we got these:
              Empty = <class __main__.Empty at 0x00AB0AB0>
              Excpt = <class '__main__.Excpt '>
              The first class (Empty) passes the PyClass_Check macro, the second one
              (Excpt) evidently fails. I'll need to dig deeper. Meanwhile, I still
              have to wonder why the code doesn't allow __class_ to be assigned a
              type instead of a class. Why can't we do this in the C code (assuming
              the appropriate PyType_Check macro):

              if (v == NULL || !(PyClass_Check (v) || PyType_Check(v) )) {

              Comment

              • samwyse

                #22
                Re: Assignments to __class_ broken in Python 2.5?

                (Yes, I probably should have said CPython in my subject, not Python.
                Sorry.)

                On Jul 13, 12:56 am, samwyse <samw...@gmail. comwrote:
                OK, in classobject.h, we find this:
                >
                #define PyClass_Check(o p) ((op)->ob_type == &PyClass_Typ e)
                >
                That seems straightforward enough. And the relevant message appears
                in classobject.c here:
                >
                static int
                instance_setatt r(PyInstanceObj ect *inst, PyObject *name, PyObject *v)
                [...]
                if (strcmp(sname, "__class__" ) == 0) {
                if (v == NULL || !PyClass_Check( v)) {
                PyErr_SetString (PyExc_TypeErro r,
                "__class__ must be set to a class");
                return -1;
                }
                >
                Back in our test code, we got these:
                >
                Empty = <class __main__.Empty at 0x00AB0AB0>
                Excpt = <class '__main__.Excpt '>
                >
                The first class (Empty) passes the PyClass_Check macro, the second one
                (Excpt) evidently fails. I'll need to dig deeper. Meanwhile, I still
                have to wonder why the code doesn't allow __class_ to be assigned a
                type instead of a class. Why can't we do this in the C code (assuming
                the appropriate PyType_Check macro):
                >
                if (v == NULL || !(PyClass_Check (v) || PyType_Check(v) )) {
                After a good night's sleep, I can see that Empty is a "real" class;
                i.e. its repr() is handled by class_repr() in classobject.c. Excpt,
                on the other hand, is a type; i.e. its repr is handled by type_repr()
                in typeobject.c. (You can tell because class_repr() returns a value
                formatted as "<class %s.%s at %p>" whereas type_repr returns a value
                formatted as "<%s '%s.%s'>", where the first %s gets filled with
                either "type" or "class".)

                This is looking more and more like a failure to abide by PEP 252/253.
                I think that the patch is simple, but I'm unusre of the
                ramifications. I also haven't looked at the 2.4 source to see how
                things used to work. Still, I think that I've got a work-around for
                OP's problem, I just need to test it under both 2.4 and 2.5.

                Comment

                • samwyse

                  #23
                  Re: Re-raising exceptions with modified message

                  On Jul 13, 12:45 am, Christoph Zwerschke <c...@online.de wrote:
                  samwyse wrote:
                  TypeError: __class__ must be set to a class
                  >
                  Excpt ceratinly appears to be a class. Does anyone smarter than me
                  know what's going on here?
                  >
                  Not that I want to appear smarter, but I think the problem here is that
                  exceptions are new-style classes now, whereas Empty is an old-style
                  class. But even if you define Empty as a new-style class, it will not
                  work, you get:
                  >
                  TypeError: __class__ assignment: only for heap types
                  >
                  This tells us that we cannot change the attributes of a built-in
                  exception. If it would be possible, I simply would have overridden the
                  __str__ method of the original exception in the first place.
                  >
                  -- Chris
                  Chris, you owe me a beer if you're ever in St. Louis, or I'm ever in
                  Germany.

                  # ----- CUT HERE -----

                  # Written by Sam Denton <samwyse@gmail. com>
                  # You may use, copy, or distribute this work,
                  # as long as you give credit to the original author.

                  # tested successfully under Python 2.4.1, 2.4.3, 2.5.1

                  """
                  On Jul 5, 2007, at 8:53 am, Christoph Zwerschke <c...@online.de >
                  wrote:
                  What is the best way to re-raise any exception with a message
                  supplemented with additional information (e.g. line number in a
                  template)? Let's say for simplicity I just want to add "sorry" to
                  every exception message.
                  Here is an example of typical usage:
                  >>def typical_usage(c ode):
                  .... try:
                  .... code()
                  .... except Exception, e:
                  .... simplicity = lambda self: str(e) + ", sorry!"
                  .... raise modify_message( e, simplicity)

                  Note that if we want to re-cycle the original exception's message,
                  then we need our re-formatter (here called 'simplicity') to be
                  defined inside the exception handler. I tried verious approaches
                  to defining the re-formater, but all of them eventually needed a
                  closure; I decided that I liked this approach best.

                  This harness wraps the example so that doctest doesn't get upset.
                  >>def test_harness(co de):
                  .... try:
                  .... typical_usage(c ode)
                  .... except Exception, e:
                  .... print "%s: %s" % (e.__class__.__ name__, str(e))

                  Now for some test cases:
                  >>test_harness( lambda: 1/0)
                  ZeroDivisionErr or: integer division or modulo by zero, sorry!
                  >>test_harness( lambda: unicode('\xe4') )
                  UnicodeDecodeEr ror: 'ascii' codec can't decode byte 0xe4 in position
                  0: ordinal not in range(128), sorry!

                  """

                  def modify_message( old, f):
                  """modify_messa ge(exception, mutator) --exception

                  Modifies the string representation of an exception.
                  """
                  class NewStyle(old.__ class__):
                  def __init__(self): pass
                  NewStyle.__name __ = old.__class__._ _name__
                  NewStyle.__str_ _ = f
                  new = NewStyle()
                  new.__dict__ = old.__dict__.co py()
                  return new

                  def _test():
                  import doctest
                  return doctest.testmod (verbose=True)

                  if __name__ == "__main__":
                  _test()

                  Comment

                  • Christoph Zwerschke

                    #24
                    Re: Re-raising exceptions with modified message

                    samwyse wrote:
                    NewStyle.__name __ = old.__class__._ _name__
                    Simple, but that does the trick!
                    new.__dict__ = old.__dict__.co py()
                    Unfortunately, that does not work, since the attributes are not
                    writeable and thus do not appear in __dict__.

                    But my __getattr__ solution does not work either, since the attributes
                    are set to None when initialized, so __getattr__ is never called.

                    Need to think about this point some more...

                    Anyway, the beer is on me ;-)

                    -- Chris

                    Comment

                    • Christoph Zwerschke

                      #25
                      Re: Re-raising exceptions with modified message

                      Christoph Zwerschke wrote:
                      But my __getattr__ solution does not work either, since the attributes
                      are set to None when initialized, so __getattr__ is never called.
                      Here is a simple solution, but it depends on the existence of the args
                      attribute that "will eventually be deprecated" according to the docs:

                      def PoliteException (e):
                      E = e.__class__
                      class PoliteException (E):
                      def __str__(self):
                      return str(e) + ", sorry!"
                      PoliteException .__name__ = E.__name__
                      return PoliteException (*e.args)

                      try:
                      unicode('\xe4')
                      except Exception, e:
                      p = PoliteException (e)
                      assert p.reason == e.reason
                      raise p

                      Comment

                      • Christoph Zwerschke

                        #26
                        Re: Re-raising exceptions with modified message

                        Christoph Zwerschke wrote:
                        Here is a simple solution, but it depends on the existence of the args
                        attribute that "will eventually be deprecated" according to the docs:
                        Ok, here is another solution that does not depend on args:

                        def PoliteException (e):
                        E = e.__class__
                        class PoliteException (E):
                        def __init__(self):
                        for arg in dir(e):
                        if not arg.startswith( '_'):
                        setattr(self, arg, getattr(e, arg))
                        def __str__(self):
                        return str(e) + ", sorry!"
                        PoliteException .__name__ = E.__name__
                        return PoliteException ()

                        try:
                        unicode('\xe4')
                        except Exception, e:
                        p = PoliteException (e)
                        assert p.reason == e.reason
                        raise p

                        Comment

                        • Christoph Zwerschke

                          #27
                          Re: Re-raising exceptions with modified message

                          Christoph Zwerschke wrote:
                          Here is a simple solution, but it depends on the existence of the args
                          attribute that "will eventually be deprecated" according to the docs:
                          Just found another amazingly simple solution that does neither use teh
                          ..args (docs: "will eventually be deprecated") attribute nor the dir()
                          function (docs: "its detailed behavior may change across releases").
                          Instead it relies on the fact that the exception itselfs behaves like
                          its args tuple (checked with Py 2.3, 2.4 and 2.5).

                          As another twist, I set the wrapper exception module to the module of
                          the original exception so that the output looks more like the output of
                          the original exception (i.e. simply "UnicodeDecodeE rror" instead of
                          "__main__.Unico deDecodeError") .

                          The code now looks like this:

                          def PoliteException (e):
                          E = e.__class__
                          class PoliteException (E):
                          def __str__(self):
                          return str(e) + ", sorry!"
                          PoliteException .__name__ = E.__name__
                          PoliteException .__module__ = E.__module__
                          return PoliteException (*e)

                          try:
                          unicode('\xe4')
                          except Exception, e:
                          p = PoliteException (e)
                          assert p.reason == e.reason
                          raise p

                          Comment

                          • fumanchu

                            #28
                            Re: Re-raising exceptions with modified message

                            On Jul 15, 2:55 am, Christoph Zwerschke <c...@online.de wrote:
                            Here is a simple solution, but it depends
                            on the existence of the args attribute that
                            "will eventually be deprecated" according
                            to the docs
                            If you don't mind using .args, then the solution is usually as simple
                            as:


                            try:
                            Thing.do(arg1, arg2)
                            except Exception, e:
                            e.args += (Thing.state, arg1, arg2)
                            raise


                            No over-engineering needed. ;)


                            Robert Brewer
                            System Architect
                            Amor Ministries
                            fumanchu@amor.o rg

                            Comment

                            • Gabriel Genellina

                              #29
                              Re: Re-raising exceptions with modified message

                              En Mon, 16 Jul 2007 13:50:50 -0300, fumanchu <fumanchu@amor. orgescribió:
                              On Jul 15, 2:55 am, Christoph Zwerschke <c...@online.de wrote:
                              >Here is a simple solution, but it depends
                              >on the existence of the args attribute that
                              >"will eventually be deprecated" according
                              >to the docs
                              >
                              If you don't mind using .args, then the solution is usually as simple
                              as:
                              >
                              >
                              try:
                              Thing.do(arg1, arg2)
                              except Exception, e:
                              e.args += (Thing.state, arg1, arg2)
                              raise
                              >
                              >
                              No over-engineering needed. ;)
                              If you read enough of this long thread, you'll see that the original
                              requirement was to enhance the *message* displayed by a normal traceback -
                              the OP has no control over the callers, but wants to add useful
                              information to any exception.
                              Your code does not qualify:

                              pytry:
                              .... open("a file that does not exist")
                              .... except Exception,e:
                              .... e.args += ("Sorry",)
                              .... raise
                              ....
                              Traceback (most recent call last):
                              File "<stdin>", line 2, in <module>
                              IOError: [Errno 2] No such file or directory: 'a file that does not exist'
                              pytry:
                              .... x = u"á".encode("as cii")
                              .... except Exception,e:
                              .... e.args += ("Sorry",)
                              .... raise
                              ....
                              Traceback (most recent call last):
                              File "<stdin>", line 2, in <module>
                              UnicodeEncodeEr ror: 'ascii' codec can't encode character u'\xe1' in
                              position 0:
                              ordinal not in range(128)

                              --
                              Gabriel Genellina

                              Comment

                              Working...